From 837215b2061625c579a20e69316550c067fc0ee8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nihat=20Uygar=20K=C3=B6seer?= Date: Thu, 29 Aug 2024 21:43:50 +0300 Subject: [PATCH] Handle window resize event (CSI t, resize) (#17721) `ResizeWindow` event in `TerminalApi` is handled and bubbled to `TerminalApi->ControlCore->TermControl->TerminalPage->AppHost`. Resizing is accepted only if the window is not in fullscreen or quake mode, and has 1 tab and pane. Relevant issues: #5094 --- src/cascadia/TerminalApp/TerminalPage.cpp | 27 ++++++++++ src/cascadia/TerminalApp/TerminalPage.h | 2 + src/cascadia/TerminalApp/TerminalPage.idl | 1 + src/cascadia/TerminalApp/TerminalWindow.cpp | 37 ++++++++++++++ src/cascadia/TerminalApp/TerminalWindow.h | 2 + src/cascadia/TerminalApp/TerminalWindow.idl | 1 + .../TerminalConnection/ConptyConnection.cpp | 8 +++ .../TerminalConnection/ConptyConnection.h | 1 + .../TerminalConnection/ConptyConnection.idl | 1 + src/cascadia/TerminalControl/ControlCore.cpp | 9 ++++ src/cascadia/TerminalControl/ControlCore.h | 2 + src/cascadia/TerminalControl/ControlCore.idl | 1 + src/cascadia/TerminalControl/EventArgs.cpp | 1 + src/cascadia/TerminalControl/EventArgs.h | 15 ++++++ src/cascadia/TerminalControl/EventArgs.idl | 6 +++ src/cascadia/TerminalControl/TermControl.cpp | 51 +++++++++++++++++++ src/cascadia/TerminalControl/TermControl.h | 5 ++ src/cascadia/TerminalControl/TermControl.idl | 1 + src/cascadia/TerminalCore/Terminal.cpp | 5 ++ src/cascadia/TerminalCore/Terminal.hpp | 4 +- src/cascadia/TerminalCore/TerminalApi.cpp | 15 +++++- src/cascadia/WindowsTerminal/AppHost.cpp | 46 +++++++++++++++++ src/cascadia/WindowsTerminal/AppHost.h | 6 +++ .../WindowsTerminal/NonClientIslandWindow.cpp | 5 +- 24 files changed, 248 insertions(+), 4 deletions(-) diff --git a/src/cascadia/TerminalApp/TerminalPage.cpp b/src/cascadia/TerminalApp/TerminalPage.cpp index 8b43dbbfe6..b3111e9b63 100644 --- a/src/cascadia/TerminalApp/TerminalPage.cpp +++ b/src/cascadia/TerminalApp/TerminalPage.cpp @@ -1743,6 +1743,8 @@ namespace winrt::TerminalApp::implementation term.SearchMissingCommand({ get_weak(), &TerminalPage::_SearchMissingCommandHandler }); + term.WindowSizeChanged({ get_weak(), &TerminalPage::_WindowSizeChanged }); + // Don't even register for the event if the feature is compiled off. if constexpr (Feature_ShellCompletions::IsEnabled()) { @@ -3090,6 +3092,31 @@ namespace winrt::TerminalApp::implementation term.RefreshQuickFixMenu(); } + void TerminalPage::_WindowSizeChanged(const IInspectable sender, const Microsoft::Terminal::Control::WindowSizeChangedEventArgs args) + { + // Raise if: + // - Not in quake mode + // - Not in fullscreen + // - Only one tab exists + // - Only one pane exists + // else: + // - Reset conpty to its original size back + if (!WindowProperties().IsQuakeWindow() && !Fullscreen() && + NumberOfTabs() == 1 && _GetFocusedTabImpl()->GetLeafPaneCount() == 1) + { + WindowSizeChanged.raise(*this, args); + } + else if (const auto& control{ sender.try_as() }) + { + const auto& connection = control.Connection(); + + if (const auto& conpty{ connection.try_as() }) + { + conpty.ResetSize(); + } + } + } + // Method Description: // - Paste text from the Windows Clipboard to the focused terminal void TerminalPage::_PasteText() diff --git a/src/cascadia/TerminalApp/TerminalPage.h b/src/cascadia/TerminalApp/TerminalPage.h index a957690a96..56c6649e18 100644 --- a/src/cascadia/TerminalApp/TerminalPage.h +++ b/src/cascadia/TerminalApp/TerminalPage.h @@ -195,6 +195,7 @@ namespace winrt::TerminalApp::implementation til::typed_event IdentifyWindowsRequested; til::typed_event RenameWindowRequested; til::typed_event SummonWindowRequested; + til::typed_event WindowSizeChanged; til::typed_event CloseRequested; til::typed_event OpenSystemMenu; @@ -541,6 +542,7 @@ namespace winrt::TerminalApp::implementation Windows::Foundation::IAsyncAction _SearchMissingCommandHandler(const IInspectable sender, const winrt::Microsoft::Terminal::Control::SearchMissingCommandEventArgs args); Windows::Foundation::IAsyncOperation> _FindPackageAsync(hstring query); + void _WindowSizeChanged(const IInspectable sender, const winrt::Microsoft::Terminal::Control::WindowSizeChangedEventArgs args); safe_void_coroutine _windowPropertyChanged(const IInspectable& sender, const winrt::Windows::UI::Xaml::Data::PropertyChangedEventArgs& args); void _onTabDragStarting(const winrt::Microsoft::UI::Xaml::Controls::TabView& sender, const winrt::Microsoft::UI::Xaml::Controls::TabViewTabDragStartingEventArgs& e); diff --git a/src/cascadia/TerminalApp/TerminalPage.idl b/src/cascadia/TerminalApp/TerminalPage.idl index 9e27235140..56d30eb3d6 100644 --- a/src/cascadia/TerminalApp/TerminalPage.idl +++ b/src/cascadia/TerminalApp/TerminalPage.idl @@ -100,6 +100,7 @@ namespace TerminalApp event Windows.Foundation.TypedEventHandler IdentifyWindowsRequested; event Windows.Foundation.TypedEventHandler RenameWindowRequested; event Windows.Foundation.TypedEventHandler SummonWindowRequested; + event Windows.Foundation.TypedEventHandler WindowSizeChanged; event Windows.Foundation.TypedEventHandler CloseRequested; event Windows.Foundation.TypedEventHandler OpenSystemMenu; diff --git a/src/cascadia/TerminalApp/TerminalWindow.cpp b/src/cascadia/TerminalApp/TerminalWindow.cpp index 9d08855a67..6a974b5c2d 100644 --- a/src/cascadia/TerminalApp/TerminalWindow.cpp +++ b/src/cascadia/TerminalApp/TerminalWindow.cpp @@ -15,6 +15,7 @@ using namespace winrt::Windows::ApplicationModel; using namespace winrt::Windows::ApplicationModel::DataTransfer; +using namespace winrt::Windows::Graphics::Display; using namespace winrt::Windows::UI::Xaml; using namespace winrt::Windows::UI::Xaml::Controls; using namespace winrt::Windows::UI::Core; @@ -217,6 +218,7 @@ namespace winrt::TerminalApp::implementation _root->SetSettings(_settings, false); // We're on our UI thread right now, so this is safe _root->Loaded({ get_weak(), &TerminalWindow::_OnLoaded }); _root->Initialized({ get_weak(), &TerminalWindow::_pageInitialized }); + _root->WindowSizeChanged({ get_weak(), &TerminalWindow::_WindowSizeChanged }); _root->Create(); AppLogic::Current()->SettingsChanged({ get_weak(), &TerminalWindow::UpdateSettingsHandler }); @@ -1331,6 +1333,41 @@ namespace winrt::TerminalApp::implementation } } + void TerminalWindow::_WindowSizeChanged(const IInspectable&, winrt::Microsoft::Terminal::Control::WindowSizeChangedEventArgs args) + { + winrt::Windows::Foundation::Size pixelSize = { static_cast(args.Width()), static_cast(args.Height()) }; + const auto scale = static_cast(DisplayInformation::GetForCurrentView().RawPixelsPerViewPixel()); + + if (!FocusMode()) + { + if (!_settings.GlobalSettings().AlwaysShowTabs()) + { + // Hide the title bar = off, Always show tabs = off. + static constexpr auto titlebarHeight = 10; + pixelSize.Height += (titlebarHeight)*scale; + } + else if (!_settings.GlobalSettings().ShowTabsInTitlebar()) + { + // Hide the title bar = off, Always show tabs = on. + static constexpr auto titlebarAndTabBarHeight = 40; + pixelSize.Height += (titlebarAndTabBarHeight)*scale; + } + // Hide the title bar = on, Always show tabs = on. + // In this case, we don't add any height because + // NonClientIslandWindow::GetTotalNonClientExclusiveSize() gets + // called in AppHost::_resizeWindow and it already takes title bar + // height into account. In other cases above + // IslandWindow::GetTotalNonClientExclusiveSize() is called, and it + // doesn't take the title bar height into account, so we have to do + // the calculation manually. + } + + args.Width(static_cast(pixelSize.Width)); + args.Height(static_cast(pixelSize.Height)); + + WindowSizeChanged.raise(*this, args); + } + winrt::hstring WindowProperties::WindowName() const noexcept { return _WindowName; diff --git a/src/cascadia/TerminalApp/TerminalWindow.h b/src/cascadia/TerminalApp/TerminalWindow.h index ee731f2ac3..29eda95158 100644 --- a/src/cascadia/TerminalApp/TerminalWindow.h +++ b/src/cascadia/TerminalApp/TerminalWindow.h @@ -162,6 +162,7 @@ namespace winrt::TerminalApp::implementation til::typed_event IsQuakeWindowChanged; til::typed_event SystemMenuChangeRequested; til::typed_event SettingsChanged; + til::typed_event WindowSizeChanged; private: // If you add controls here, but forget to null them either here or in @@ -202,6 +203,7 @@ namespace winrt::TerminalApp::implementation void _OnLoaded(const IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& eventArgs); void _pageInitialized(const IInspectable& sender, const IInspectable& eventArgs); void _OpenSettingsUI(); + void _WindowSizeChanged(const IInspectable& sender, winrt::Microsoft::Terminal::Control::WindowSizeChangedEventArgs args); winrt::Windows::Foundation::Collections::IVector _contentStringToActions(const winrt::hstring& content, const bool replaceFirstWithNewTab); diff --git a/src/cascadia/TerminalApp/TerminalWindow.idl b/src/cascadia/TerminalApp/TerminalWindow.idl index 2cf4b31729..8b1bca78f9 100644 --- a/src/cascadia/TerminalApp/TerminalWindow.idl +++ b/src/cascadia/TerminalApp/TerminalWindow.idl @@ -132,6 +132,7 @@ namespace TerminalApp event Windows.Foundation.TypedEventHandler QuitRequested; event Windows.Foundation.TypedEventHandler SystemMenuChangeRequested; event Windows.Foundation.TypedEventHandler ShowWindowChanged; + event Windows.Foundation.TypedEventHandler WindowSizeChanged; event Windows.Foundation.TypedEventHandler SettingsChanged; diff --git a/src/cascadia/TerminalConnection/ConptyConnection.cpp b/src/cascadia/TerminalConnection/ConptyConnection.cpp index 9ef8abdc23..0a0b26aa93 100644 --- a/src/cascadia/TerminalConnection/ConptyConnection.cpp +++ b/src/cascadia/TerminalConnection/ConptyConnection.cpp @@ -534,6 +534,14 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation } } + void ConptyConnection::ResetSize() + { + if (_isConnected()) + { + THROW_IF_FAILED(ConptyResizePseudoConsole(_hPC.get(), { Utils::ClampToShortMax(_cols, 1), Utils::ClampToShortMax(_rows, 1) })); + } + } + void ConptyConnection::ClearBuffer() { // If we haven't connected yet, then we really don't need to do diff --git a/src/cascadia/TerminalConnection/ConptyConnection.h b/src/cascadia/TerminalConnection/ConptyConnection.h index ec11f4a709..ca67588f64 100644 --- a/src/cascadia/TerminalConnection/ConptyConnection.h +++ b/src/cascadia/TerminalConnection/ConptyConnection.h @@ -23,6 +23,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation void Start(); void WriteInput(const winrt::array_view buffer); void Resize(uint32_t rows, uint32_t columns); + void ResetSize(); void Close() noexcept; void ClearBuffer(); diff --git a/src/cascadia/TerminalConnection/ConptyConnection.idl b/src/cascadia/TerminalConnection/ConptyConnection.idl index 8e0ad44c62..09466b9d11 100644 --- a/src/cascadia/TerminalConnection/ConptyConnection.idl +++ b/src/cascadia/TerminalConnection/ConptyConnection.idl @@ -14,6 +14,7 @@ namespace Microsoft.Terminal.TerminalConnection String StartingTitle { get; }; UInt16 ShowWindow { get; }; + void ResetSize(); void ClearBuffer(); void ShowHide(Boolean show); diff --git a/src/cascadia/TerminalControl/ControlCore.cpp b/src/cascadia/TerminalControl/ControlCore.cpp index 9be62aad0c..cd4f5f58b8 100644 --- a/src/cascadia/TerminalControl/ControlCore.cpp +++ b/src/cascadia/TerminalControl/ControlCore.cpp @@ -134,6 +134,9 @@ namespace winrt::Microsoft::Terminal::Control::implementation auto pfnClearQuickFix = [this] { ClearQuickFix(); }; _terminal->SetClearQuickFixCallback(pfnClearQuickFix); + auto pfnWindowSizeChanged = [this](auto&& PH1, auto&& PH2) { _terminalWindowSizeChanged(std::forward(PH1), std::forward(PH2)); }; + _terminal->SetWindowSizeChangedCallback(pfnWindowSizeChanged); + // MSFT 33353327: Initialize the renderer in the ctor instead of Initialize(). // We need the renderer to be ready to accept new engines before the SwapChainPanel is ready to go. // If we wait, a screen reader may try to get the AutomationPeer (aka the UIA Engine), and we won't be able to attach @@ -1629,6 +1632,12 @@ namespace winrt::Microsoft::Terminal::Control::implementation _midiAudio.PlayNote(reinterpret_cast(_owningHwnd), noteNumber, velocity, std::chrono::duration_cast(duration)); } + void ControlCore::_terminalWindowSizeChanged(int32_t width, int32_t height) + { + auto size = winrt::make(width, height); + WindowSizeChanged.raise(*this, size); + } + void ControlCore::_terminalSearchMissingCommand(std::wstring_view missingCommand, const til::CoordType& bufferRow) { SearchMissingCommand.raise(*this, make(hstring{ missingCommand }, bufferRow)); diff --git a/src/cascadia/TerminalControl/ControlCore.h b/src/cascadia/TerminalControl/ControlCore.h index 648cee1591..9ac8e5450c 100644 --- a/src/cascadia/TerminalControl/ControlCore.h +++ b/src/cascadia/TerminalControl/ControlCore.h @@ -295,6 +295,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation til::typed_event CompletionsChanged; til::typed_event SearchMissingCommand; til::typed_event<> RefreshQuickFixUI; + til::typed_event WindowSizeChanged; til::typed_event<> CloseTerminalRequested; til::typed_event<> RestartTerminalRequested; @@ -391,6 +392,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation const int velocity, const std::chrono::microseconds duration); void _terminalSearchMissingCommand(std::wstring_view missingCommand, const til::CoordType& bufferRow); + void _terminalWindowSizeChanged(int32_t width, int32_t height); safe_void_coroutine _terminalCompletionsChanged(std::wstring_view menuJson, unsigned int replaceLength); diff --git a/src/cascadia/TerminalControl/ControlCore.idl b/src/cascadia/TerminalControl/ControlCore.idl index db1d314b23..3be52269a8 100644 --- a/src/cascadia/TerminalControl/ControlCore.idl +++ b/src/cascadia/TerminalControl/ControlCore.idl @@ -190,6 +190,7 @@ namespace Microsoft.Terminal.Control event Windows.Foundation.TypedEventHandler ShowWindowChanged; event Windows.Foundation.TypedEventHandler SearchMissingCommand; event Windows.Foundation.TypedEventHandler RefreshQuickFixUI; + event Windows.Foundation.TypedEventHandler WindowSizeChanged; // These events are always called from the UI thread (bugs aside) event Windows.Foundation.TypedEventHandler FontSizeChanged; diff --git a/src/cascadia/TerminalControl/EventArgs.cpp b/src/cascadia/TerminalControl/EventArgs.cpp index d0631531a9..b7e90a7511 100644 --- a/src/cascadia/TerminalControl/EventArgs.cpp +++ b/src/cascadia/TerminalControl/EventArgs.cpp @@ -19,3 +19,4 @@ #include "CharSentEventArgs.g.cpp" #include "StringSentEventArgs.g.cpp" #include "SearchMissingCommandEventArgs.g.cpp" +#include "WindowSizeChangedEventArgs.g.cpp" diff --git a/src/cascadia/TerminalControl/EventArgs.h b/src/cascadia/TerminalControl/EventArgs.h index 0e98cefd67..526aa2b033 100644 --- a/src/cascadia/TerminalControl/EventArgs.h +++ b/src/cascadia/TerminalControl/EventArgs.h @@ -19,6 +19,7 @@ #include "CharSentEventArgs.g.h" #include "StringSentEventArgs.g.h" #include "SearchMissingCommandEventArgs.g.h" +#include "WindowSizeChangedEventArgs.g.h" namespace winrt::Microsoft::Terminal::Control::implementation { @@ -223,6 +224,20 @@ namespace winrt::Microsoft::Terminal::Control::implementation til::property MissingCommand; til::property BufferRow; }; + + struct WindowSizeChangedEventArgs : public WindowSizeChangedEventArgsT + { + public: + WindowSizeChangedEventArgs(int32_t width, + int32_t height) : + _Width(width), + _Height(height) + { + } + + WINRT_PROPERTY(int32_t, Width); + WINRT_PROPERTY(int32_t, Height); + }; } namespace winrt::Microsoft::Terminal::Control::factory_implementation diff --git a/src/cascadia/TerminalControl/EventArgs.idl b/src/cascadia/TerminalControl/EventArgs.idl index c3255bc667..de859743b6 100644 --- a/src/cascadia/TerminalControl/EventArgs.idl +++ b/src/cascadia/TerminalControl/EventArgs.idl @@ -132,4 +132,10 @@ namespace Microsoft.Terminal.Control String MissingCommand { get; }; Int32 BufferRow { get; }; } + + runtimeclass WindowSizeChangedEventArgs + { + Int32 Width; + Int32 Height; + } } diff --git a/src/cascadia/TerminalControl/TermControl.cpp b/src/cascadia/TerminalControl/TermControl.cpp index 765eb811fd..565ea02ccf 100644 --- a/src/cascadia/TerminalControl/TermControl.cpp +++ b/src/cascadia/TerminalControl/TermControl.cpp @@ -224,6 +224,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation _revokers.CompletionsChanged = _core.CompletionsChanged(winrt::auto_revoke, { get_weak(), &TermControl::_bubbleCompletionsChanged }); _revokers.RestartTerminalRequested = _core.RestartTerminalRequested(winrt::auto_revoke, { get_weak(), &TermControl::_bubbleRestartTerminalRequested }); _revokers.SearchMissingCommand = _core.SearchMissingCommand(winrt::auto_revoke, { get_weak(), &TermControl::_bubbleSearchMissingCommand }); + _revokers.WindowSizeChanged = _core.WindowSizeChanged(winrt::auto_revoke, { get_weak(), &TermControl::_bubbleWindowSizeChanged }); _revokers.PasteFromClipboard = _interactivity.PasteFromClipboard(winrt::auto_revoke, { get_weak(), &TermControl::_bubblePasteFromClipboard }); @@ -2779,6 +2780,42 @@ namespace winrt::Microsoft::Terminal::Control::implementation return { width, height }; } + // Function Description: + // - Calculates new dimensions (in pixels) from row and column counts. + // Arguments: + // - dpi: The dpi value. + // - sizeInChars: The size to get the new dimensions for. + // Return Value: + // - a size containing the requested dimensions in pixels. + winrt::Windows::Foundation::Size TermControl::GetNewDimensions(const winrt::Windows::Foundation::Size& sizeInChars) + { + const auto cols = ::base::saturated_cast(sizeInChars.Width); + const auto rows = ::base::saturated_cast(sizeInChars.Height); + const auto fontSize = _core.FontSize(); + const auto scrollState = _core.Settings().ScrollState(); + const auto padding = _core.Settings().Padding(); + const auto scale = static_cast(DisplayInformation::GetForCurrentView().RawPixelsPerViewPixel()); + float width = cols * static_cast(fontSize.Width); + float height = rows * static_cast(fontSize.Height); + + // Reserve additional space if scrollbar is intended to be visible + if (scrollState != ScrollbarState::Hidden) + { + // UWP XAML scrollbars aren't guaranteed to be the same size as the + // ComCtl scrollbars, but it's certainly close enough. + const auto dpi = ::base::saturated_cast(USER_DEFAULT_SCREEN_DPI * scale); + const auto scrollbarSize = GetSystemMetricsForDpi(SM_CXVSCROLL, dpi); + width += scrollbarSize; + } + + const auto thickness = ParseThicknessFromPadding(padding); + // GH#2061 - make sure to account for the size the padding _will be_ scaled to + width += scale * static_cast(thickness.Left + thickness.Right); + height += scale * static_cast(thickness.Top + thickness.Bottom); + + return { width, height }; + } + // Method Description: // - Get the size of a single character of this control. The size is in // _pixels_. If you want it in DIPs, you'll need to DIVIDE by the @@ -4088,6 +4125,20 @@ namespace winrt::Microsoft::Terminal::Control::implementation SearchMissingCommand.raise(*this, args); } + winrt::fire_and_forget TermControl::_bubbleWindowSizeChanged(const IInspectable& /*sender*/, Control::WindowSizeChangedEventArgs args) + { + auto weakThis{ get_weak() }; + co_await wil::resume_foreground(Dispatcher()); + + if (auto control{ weakThis.get() }) + { + winrt::Windows::Foundation::Size cellCount{ static_cast(args.Width()), static_cast(args.Height()) }; + const auto pixelSize = GetNewDimensions(cellCount); + + WindowSizeChanged.raise(*this, winrt::make(static_cast(pixelSize.Width), static_cast(pixelSize.Height))); + } + } + til::CoordType TermControl::_calculateSearchScrollOffset() const { auto result = 0; diff --git a/src/cascadia/TerminalControl/TermControl.h b/src/cascadia/TerminalControl/TermControl.h index caeadba45f..d8a44161db 100644 --- a/src/cascadia/TerminalControl/TermControl.h +++ b/src/cascadia/TerminalControl/TermControl.h @@ -159,6 +159,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation int32_t commandlineRows); static Windows::Foundation::Size GetProposedDimensions(const IControlSettings& settings, const uint32_t dpi, const winrt::Windows::Foundation::Size& initialSizeInChars); + winrt::Windows::Foundation::Size GetNewDimensions(const winrt::Windows::Foundation::Size& initialSizeInChars); + void BellLightOn(); bool ReadOnly() const noexcept; @@ -211,6 +213,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation til::typed_event CharSent; til::typed_event StringSent; til::typed_event SearchMissingCommand; + til::typed_event WindowSizeChanged; // UNDER NO CIRCUMSTANCES SHOULD YOU ADD A (PROJECTED_)FORWARDED_TYPED_EVENT HERE // Those attach the handler to the core directly, and will explode if @@ -437,6 +440,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation void _showContextMenuAt(const til::point& controlRelativePos); void _bubbleSearchMissingCommand(const IInspectable& sender, const Control::SearchMissingCommandEventArgs& args); + winrt::fire_and_forget _bubbleWindowSizeChanged(const IInspectable& sender, Control::WindowSizeChangedEventArgs args); til::CoordType _calculateSearchScrollOffset() const; void _PasteCommandHandler(const IInspectable& sender, const IInspectable& args); @@ -470,6 +474,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation Control::ControlCore::RestartTerminalRequested_revoker RestartTerminalRequested; Control::ControlCore::SearchMissingCommand_revoker SearchMissingCommand; Control::ControlCore::RefreshQuickFixUI_revoker RefreshQuickFixUI; + Control::ControlCore::WindowSizeChanged_revoker WindowSizeChanged; // These are set up in _InitializeTerminal Control::ControlCore::RendererWarning_revoker RendererWarning; diff --git a/src/cascadia/TerminalControl/TermControl.idl b/src/cascadia/TerminalControl/TermControl.idl index 7b643ad1cb..c60b588454 100644 --- a/src/cascadia/TerminalControl/TermControl.idl +++ b/src/cascadia/TerminalControl/TermControl.idl @@ -63,6 +63,7 @@ namespace Microsoft.Terminal.Control event Windows.Foundation.TypedEventHandler TabColorChanged; event Windows.Foundation.TypedEventHandler ReadOnlyChanged; event Windows.Foundation.TypedEventHandler FocusFollowMouseRequested; + event Windows.Foundation.TypedEventHandler WindowSizeChanged; event Windows.Foundation.TypedEventHandler CompletionsChanged; diff --git a/src/cascadia/TerminalCore/Terminal.cpp b/src/cascadia/TerminalCore/Terminal.cpp index b745d66f70..c5876e1497 100644 --- a/src/cascadia/TerminalCore/Terminal.cpp +++ b/src/cascadia/TerminalCore/Terminal.cpp @@ -1134,6 +1134,11 @@ void Terminal::SetShowWindowCallback(std::function pfn) noexcept _pfnShowWindowChanged.swap(pfn); } +void Terminal::SetWindowSizeChangedCallback(std::function pfn) noexcept +{ + _pfnWindowSizeChanged.swap(pfn); +} + // Method Description: // - Allows setting a callback for playing MIDI notes. // Arguments: diff --git a/src/cascadia/TerminalCore/Terminal.hpp b/src/cascadia/TerminalCore/Terminal.hpp index 34d92213d8..e9d3f1abd0 100644 --- a/src/cascadia/TerminalCore/Terminal.hpp +++ b/src/cascadia/TerminalCore/Terminal.hpp @@ -139,7 +139,7 @@ public: void WarningBell() override; void SetWindowTitle(const std::wstring_view title) override; CursorType GetUserDefaultCursorStyle() const noexcept override; - bool ResizeWindow(const til::CoordType width, const til::CoordType height) noexcept override; + bool ResizeWindow(const til::CoordType width, const til::CoordType height) override; void SetConsoleOutputCP(const unsigned int codepage) noexcept override; unsigned int GetConsoleOutputCP() const noexcept override; void CopyToClipboard(wil::zwstring_view content) override; @@ -232,6 +232,7 @@ public: void CompletionsChangedCallback(std::function pfn) noexcept; void SetSearchMissingCommandCallback(std::function pfn) noexcept; void SetClearQuickFixCallback(std::function pfn) noexcept; + void SetWindowSizeChangedCallback(std::function pfn) noexcept; void SetSearchHighlights(const std::vector& highlights) noexcept; void SetSearchHighlightFocused(const size_t focusedIdx, til::CoordType searchScrollOffset); @@ -344,6 +345,7 @@ private: std::function _pfnCompletionsChanged; std::function _pfnSearchMissingCommand; std::function _pfnClearQuickFix; + std::function _pfnWindowSizeChanged; RenderSettings _renderSettings; std::unique_ptr<::Microsoft::Console::VirtualTerminal::StateMachine> _stateMachine; diff --git a/src/cascadia/TerminalCore/TerminalApi.cpp b/src/cascadia/TerminalCore/TerminalApi.cpp index ffd9ec72af..40ff599e11 100644 --- a/src/cascadia/TerminalCore/TerminalApi.cpp +++ b/src/cascadia/TerminalCore/TerminalApi.cpp @@ -97,9 +97,22 @@ CursorType Terminal::GetUserDefaultCursorStyle() const noexcept return _defaultCursorShape; } -bool Terminal::ResizeWindow(const til::CoordType /*width*/, const til::CoordType /*height*/) noexcept +bool Terminal::ResizeWindow(const til::CoordType width, const til::CoordType height) { // TODO: This will be needed to support various resizing sequences. See also GH#1860. + _assertLocked(); + + if (width <= 0 || height <= 0 || width > SHRT_MAX || height > SHRT_MAX) + { + return false; + } + + if (_pfnWindowSizeChanged) + { + _pfnWindowSizeChanged(width, height); + return true; + } + return false; } diff --git a/src/cascadia/WindowsTerminal/AppHost.cpp b/src/cascadia/WindowsTerminal/AppHost.cpp index 9656d7d8fc..7c538c71cd 100644 --- a/src/cascadia/WindowsTerminal/AppHost.cpp +++ b/src/cascadia/WindowsTerminal/AppHost.cpp @@ -388,6 +388,7 @@ void AppHost::Initialize() _revokers.SetTaskbarProgress = _windowLogic.SetTaskbarProgress(winrt::auto_revoke, { this, &AppHost::SetTaskbarProgress }); _revokers.IdentifyWindowsRequested = _windowLogic.IdentifyWindowsRequested(winrt::auto_revoke, { this, &AppHost::_IdentifyWindowsRequested }); _revokers.RenameWindowRequested = _windowLogic.RenameWindowRequested(winrt::auto_revoke, { this, &AppHost::_RenameWindowRequested }); + _revokers.WindowSizeChanged = _windowLogic.WindowSizeChanged(winrt::auto_revoke, { this, &AppHost::_WindowSizeChanged }); // A note: make sure to listen to our _window_'s settings changed, not the // AppLogic's. We want to make sure the event has gone through the window @@ -725,6 +726,45 @@ void AppHost::_initialResizeAndRepositionWindow(const HWND hwnd, til::rect propo LOG_LAST_ERROR_IF(!succeeded); } +// Method Description: +// - Resize the window when window size changed signal is received. +// Arguments: +// - hwnd: The HWND of the window we're about to resize. +// - newSize: The new size of the window in pixels. +// Return Value: +// - None +void AppHost::_resizeWindow(const HWND hwnd, til::size newSize) +{ + til::rect windowRect{ _window->GetWindowRect() }; + UINT dpix = _window->GetCurrentDpi(); + + const auto islandWidth = Utils::ClampToShortMax(newSize.width, 1); + const auto islandHeight = Utils::ClampToShortMax(newSize.height, 1); + + // Get the size of a window we'd need to host that client rect. This will + // add the titlebar space. + const til::size nonClientSize{ _window->GetTotalNonClientExclusiveSize(dpix) }; + long adjustedWidth = islandWidth + nonClientSize.width; + long adjustedHeight = islandHeight + nonClientSize.height; + + til::size dimensions{ Utils::ClampToShortMax(adjustedWidth, 1), + Utils::ClampToShortMax(adjustedHeight, 1) }; + til::point origin{ windowRect.left, windowRect.top }; + + const til::rect newRect{ origin, dimensions }; + bool succeeded = SetWindowPos(hwnd, + nullptr, + newRect.left, + newRect.top, + newRect.width(), + newRect.height(), + SWP_NOACTIVATE | SWP_NOZORDER); + + // If we can't resize the window, that's really okay. We can just go on with + // the originally proposed window size. + LOG_LAST_ERROR_IF(!succeeded); +} + // Method Description: // - Called when the app wants to set its titlebar content. We'll take the // UIElement and set the Content property of our Titlebar that element. @@ -1224,6 +1264,12 @@ void AppHost::_ShowWindowChanged(const winrt::Windows::Foundation::IInspectable& _showHideWindowThrottler->Run(args.ShowOrHide()); } +void AppHost::_WindowSizeChanged(const winrt::Windows::Foundation::IInspectable& /*sender*/, + const winrt::Microsoft::Terminal::Control::WindowSizeChangedEventArgs& args) +{ + _resizeWindow(_window->GetHandle(), { args.Width(), args.Height() }); +} + void AppHost::_SummonWindowRequested(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Windows::Foundation::IInspectable&) { diff --git a/src/cascadia/WindowsTerminal/AppHost.h b/src/cascadia/WindowsTerminal/AppHost.h index 3991bc51b1..7e2d8778a2 100644 --- a/src/cascadia/WindowsTerminal/AppHost.h +++ b/src/cascadia/WindowsTerminal/AppHost.h @@ -134,6 +134,9 @@ private: void _ShowWindowChanged(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Microsoft::Terminal::Control::ShowWindowArgs& args); + void _WindowSizeChanged(const winrt::Windows::Foundation::IInspectable& sender, + const winrt::Microsoft::Terminal::Control::WindowSizeChangedEventArgs& args); + void _updateTheme(); void _PropertyChangedHandler(const winrt::Windows::Foundation::IInspectable& sender, @@ -141,6 +144,8 @@ private: void _initialResizeAndRepositionWindow(const HWND hwnd, til::rect proposedRect, winrt::Microsoft::Terminal::Settings::Model::LaunchMode& launchMode); + void _resizeWindow(const HWND hwnd, til::size newSize); + void _handleMoveContent(const winrt::Windows::Foundation::IInspectable& sender, winrt::TerminalApp::RequestMoveContentArgs args); void _handleAttach(const winrt::Windows::Foundation::IInspectable& sender, @@ -201,6 +206,7 @@ private: winrt::TerminalApp::TerminalWindow::RequestLaunchPosition_revoker RequestLaunchPosition; winrt::TerminalApp::TerminalWindow::PropertyChanged_revoker PropertyChanged; winrt::TerminalApp::TerminalWindow::SettingsChanged_revoker SettingsChanged; + winrt::TerminalApp::TerminalWindow::WindowSizeChanged_revoker WindowSizeChanged; winrt::Microsoft::Terminal::Remoting::Peasant::SendContentRequested_revoker SendContentRequested; } _revokers{}; diff --git a/src/cascadia/WindowsTerminal/NonClientIslandWindow.cpp b/src/cascadia/WindowsTerminal/NonClientIslandWindow.cpp index 73a2ea8944..8d4982269b 100644 --- a/src/cascadia/WindowsTerminal/NonClientIslandWindow.cpp +++ b/src/cascadia/WindowsTerminal/NonClientIslandWindow.cpp @@ -871,14 +871,15 @@ til::rect NonClientIslandWindow::GetNonClientFrame(UINT dpi) const noexcept til::size NonClientIslandWindow::GetTotalNonClientExclusiveSize(UINT dpi) const noexcept { const auto islandFrame{ GetNonClientFrame(dpi) }; + const auto scale = GetCurrentDpiScale(); // If we have a titlebar, this is being called after we've initialized, and // we can just ask that titlebar how big it wants to be. - const auto titleBarHeight = _titlebar ? static_cast(_titlebar.ActualHeight()) : 0; + const auto titleBarHeight = _titlebar ? static_cast(_titlebar.ActualHeight()) * scale : 0; return { islandFrame.right - islandFrame.left, - islandFrame.bottom - islandFrame.top + titleBarHeight + islandFrame.bottom - islandFrame.top + static_cast(titleBarHeight) }; }