diff --git a/.github/actions/spelling/expect/expect.txt b/.github/actions/spelling/expect/expect.txt index 32dd37878d7..28b9b790d7b 100644 --- a/.github/actions/spelling/expect/expect.txt +++ b/.github/actions/spelling/expect/expect.txt @@ -1155,6 +1155,7 @@ NOCONTEXTHELP NOCOPYBITS NODUP noexcepts +NOFONT NOINTEGRALHEIGHT NOINTERFACE NOLINKINFO diff --git a/build/pipelines/ci.yml b/build/pipelines/ci.yml index ab3490bc58b..843267b4be6 100644 --- a/build/pipelines/ci.yml +++ b/build/pipelines/ci.yml @@ -4,6 +4,7 @@ trigger: include: - main - feature/* + - gh-readonly-queue/* paths: exclude: - doc/* diff --git a/doc/cascadia/profiles.schema.json b/doc/cascadia/profiles.schema.json index f64828f921e..6f636c1caf3 100644 --- a/doc/cascadia/profiles.schema.json +++ b/doc/cascadia/profiles.schema.json @@ -2333,12 +2333,20 @@ }, "type": "array" }, - "experimental.rendering.forceFullRepaint": { - "description": "When set to true, we will redraw the entire screen each frame. When set to false, we will render only the updates to the screen between frames.", + "rendering.graphicsAPI": { + "description": "Direct3D 11 provides a more performant and feature-rich experience, whereas Direct2D is more stable. The default option \"Automatic\" will pick the API that best fits your graphics hardware. If you experience significant issues, consider using Direct2D.", + "type": "string", + "enum": [ + "direct2d", + "direct3d11" + ] + }, + "rendering.disablePartialInvalidation": { + "description": "By default, the text renderer uses a FLIP_SEQUENTIAL Swap Chain and declares dirty rectangles via the Present1 API. When this setting is enabled, a FLIP_DISCARD Swap Chain will be used instead, and no dirty rectangles will be declared. Whether one or the other is better depends on your hardware and various other factors.", "type": "boolean" }, - "experimental.rendering.software": { - "description": "When set to true, we will use the software renderer (a.k.a. WARP) instead of the hardware one.", + "rendering.software": { + "description": "When enabled, the terminal will use a software rasterizer (WARP). This setting should be left disabled under almost all circumstances.", "type": "boolean" }, "experimental.input.forceVT": { diff --git a/src/cascadia/LocalTests_TerminalApp/TabTests.cpp b/src/cascadia/LocalTests_TerminalApp/TabTests.cpp index 4a09828c680..d21497a51d9 100644 --- a/src/cascadia/LocalTests_TerminalApp/TabTests.cpp +++ b/src/cascadia/LocalTests_TerminalApp/TabTests.cpp @@ -1326,7 +1326,7 @@ namespace TerminalAppLocalTests const auto& controlSettings = activeControl.Settings(); VERIFY_IS_NOT_NULL(controlSettings); - VERIFY_ARE_EQUAL(til::color{ 0xff0c0c0c }, controlSettings.DefaultBackground()); + VERIFY_ARE_EQUAL(til::color{ 0xff0c0c0c }, til::color{ controlSettings.DefaultBackground() }); }); TestOnUIThread([&page]() { @@ -1344,7 +1344,7 @@ namespace TerminalAppLocalTests VERIFY_IS_NOT_NULL(controlSettings); Log::Comment(L"Color should be changed to the preview"); - VERIFY_ARE_EQUAL(til::color{ 0xff000000 }, controlSettings.DefaultBackground()); + VERIFY_ARE_EQUAL(til::color{ 0xff000000 }, til::color{ controlSettings.DefaultBackground() }); // And we should have stored a function to revert the change. VERIFY_ARE_EQUAL(1u, page->_restorePreviewFuncs.size()); @@ -1366,7 +1366,7 @@ namespace TerminalAppLocalTests VERIFY_IS_NOT_NULL(controlSettings); Log::Comment(L"Color should be changed"); - VERIFY_ARE_EQUAL(til::color{ 0xff000000 }, controlSettings.DefaultBackground()); + VERIFY_ARE_EQUAL(til::color{ 0xff000000 }, til::color{ controlSettings.DefaultBackground() }); // After preview there should be no more restore functions to execute. VERIFY_ARE_EQUAL(0u, page->_restorePreviewFuncs.size()); @@ -1394,7 +1394,7 @@ namespace TerminalAppLocalTests const auto& controlSettings = activeControl.Settings(); VERIFY_IS_NOT_NULL(controlSettings); - VERIFY_ARE_EQUAL(til::color{ 0xff0c0c0c }, controlSettings.DefaultBackground()); + VERIFY_ARE_EQUAL(til::color{ 0xff0c0c0c }, til::color{ controlSettings.DefaultBackground() }); }); TestOnUIThread([&page]() { @@ -1412,7 +1412,7 @@ namespace TerminalAppLocalTests VERIFY_IS_NOT_NULL(controlSettings); Log::Comment(L"Color should be changed to the preview"); - VERIFY_ARE_EQUAL(til::color{ 0xff000000 }, controlSettings.DefaultBackground()); + VERIFY_ARE_EQUAL(til::color{ 0xff000000 }, til::color{ controlSettings.DefaultBackground() }); }); TestOnUIThread([&page]() { @@ -1428,7 +1428,7 @@ namespace TerminalAppLocalTests VERIFY_IS_NOT_NULL(controlSettings); Log::Comment(L"Color should be the same as it originally was"); - VERIFY_ARE_EQUAL(til::color{ 0xff0c0c0c }, controlSettings.DefaultBackground()); + VERIFY_ARE_EQUAL(til::color{ 0xff0c0c0c }, til::color{ controlSettings.DefaultBackground() }); }); Log::Comment(L"Sleep to let events propagate"); Sleep(250); @@ -1450,7 +1450,7 @@ namespace TerminalAppLocalTests const auto& controlSettings = activeControl.Settings(); VERIFY_IS_NOT_NULL(controlSettings); - VERIFY_ARE_EQUAL(til::color{ 0xff0c0c0c }, controlSettings.DefaultBackground()); + VERIFY_ARE_EQUAL(til::color{ 0xff0c0c0c }, til::color{ controlSettings.DefaultBackground() }); }); TestOnUIThread([&page]() { @@ -1467,7 +1467,7 @@ namespace TerminalAppLocalTests VERIFY_IS_NOT_NULL(controlSettings); Log::Comment(L"Color should be changed to the preview"); - VERIFY_ARE_EQUAL(til::color{ 0xff000000 }, controlSettings.DefaultBackground()); + VERIFY_ARE_EQUAL(til::color{ 0xff000000 }, til::color{ controlSettings.DefaultBackground() }); }); TestOnUIThread([&page]() { @@ -1484,7 +1484,7 @@ namespace TerminalAppLocalTests VERIFY_IS_NOT_NULL(controlSettings); Log::Comment(L"Color should be changed to the preview"); - VERIFY_ARE_EQUAL(til::color{ 0xffFAFAFA }, controlSettings.DefaultBackground()); + VERIFY_ARE_EQUAL(til::color{ 0xffFAFAFA }, til::color{ controlSettings.DefaultBackground() }); }); TestOnUIThread([&page]() { @@ -1503,7 +1503,7 @@ namespace TerminalAppLocalTests VERIFY_IS_NOT_NULL(controlSettings); Log::Comment(L"Color should be changed"); - VERIFY_ARE_EQUAL(til::color{ 0xffFAFAFA }, controlSettings.DefaultBackground()); + VERIFY_ARE_EQUAL(til::color{ 0xffFAFAFA }, til::color{ controlSettings.DefaultBackground() }); }); Log::Comment(L"Sleep to let events propagate"); Sleep(250); diff --git a/src/cascadia/LocalTests_TerminalApp/pch.h b/src/cascadia/LocalTests_TerminalApp/pch.h index 25df5f47e9e..24162c0f936 100644 --- a/src/cascadia/LocalTests_TerminalApp/pch.h +++ b/src/cascadia/LocalTests_TerminalApp/pch.h @@ -56,6 +56,8 @@ Author(s): #include #include +#include +#include #include #include diff --git a/src/cascadia/TerminalApp/AppActionHandlers.cpp b/src/cascadia/TerminalApp/AppActionHandlers.cpp index e0738a0f8b9..c584a00eddd 100644 --- a/src/cascadia/TerminalApp/AppActionHandlers.cpp +++ b/src/cascadia/TerminalApp/AppActionHandlers.cpp @@ -5,6 +5,7 @@ #include "App.h" #include "TerminalPage.h" +#include "ScratchpadContent.h" #include "../WinRTUtils/inc/WtExeUtils.h" #include "../../types/inc/utils.hpp" #include "Utils.h" @@ -1418,7 +1419,7 @@ namespace winrt::TerminalApp::implementation { if (const auto activePane{ activeTab->GetActivePane() }) { - _restartPaneConnection(activePane); + _restartPaneConnection(activePane->GetContent().try_as(), nullptr); } } args.Handled(true); @@ -1433,6 +1434,25 @@ namespace winrt::TerminalApp::implementation } args.Handled(true); } + + void TerminalPage::_HandleOpenScratchpad(const IInspectable& /*sender*/, + const ActionEventArgs& args) + { + if (Feature_ScratchpadPane::IsEnabled()) + { + const auto& scratchPane{ winrt::make_self() }; + + // This is maybe a little wacky - add our key event handler to the pane + // we made. So that we can get actions for keys that the content didn't + // handle. + scratchPane->GetRoot().KeyDown({ this, &TerminalPage::_KeyDownHandler }); + + const auto resultPane = std::make_shared(*scratchPane); + _SplitPane(_GetFocusedTabImpl(), SplitDirection::Automatic, 0.5f, resultPane); + args.Handled(true); + } + } + void TerminalPage::_HandleOpenAbout(const IInspectable& /*sender*/, const ActionEventArgs& args) { diff --git a/src/cascadia/TerminalApp/IPaneContent.idl b/src/cascadia/TerminalApp/IPaneContent.idl new file mode 100644 index 00000000000..ebe0914ea9c --- /dev/null +++ b/src/cascadia/TerminalApp/IPaneContent.idl @@ -0,0 +1,52 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +namespace TerminalApp +{ + + runtimeclass BellEventArgs + { + Boolean FlashTaskbar { get; }; + }; + + interface IPaneContent + { + Windows.UI.Xaml.FrameworkElement GetRoot(); + + Windows.Foundation.Size MinimumSize { get; }; + + String Title { get; }; + UInt64 TaskbarState { get; }; + UInt64 TaskbarProgress { get; }; + Boolean ReadOnly { get; }; + + Microsoft.Terminal.Settings.Model.NewTerminalArgs GetNewTerminalArgs(Boolean asContent); + + void Focus(Windows.UI.Xaml.FocusState reason); + + void Close(); + + event Windows.Foundation.TypedEventHandler CloseRequested; + + event Windows.Foundation.TypedEventHandler ConnectionStateChanged; + event Windows.Foundation.TypedEventHandler BellRequested; + event Windows.Foundation.TypedEventHandler TitleChanged; + event Windows.Foundation.TypedEventHandler TabColorChanged; + event Windows.Foundation.TypedEventHandler TaskbarProgressChanged; + event Windows.Foundation.TypedEventHandler ReadOnlyChanged; + event Windows.Foundation.TypedEventHandler FocusRequested; + }; + + + enum PaneSnapDirection + { + Width, + Height + }; + + interface ISnappable + { + Single SnapDownToGrid(PaneSnapDirection direction, Single sizeToSnap); + Windows.Foundation.Size GridUnitSize { get; }; + }; +} diff --git a/src/cascadia/TerminalApp/Pane.cpp b/src/cascadia/TerminalApp/Pane.cpp index 59988d928fc..9ed53d49204 100644 --- a/src/cascadia/TerminalApp/Pane.cpp +++ b/src/cascadia/TerminalApp/Pane.cpp @@ -3,6 +3,7 @@ #include "pch.h" #include "Pane.h" + #include "AppLogic.h" #include "Utils.h" @@ -33,19 +34,21 @@ static const int CombinedPaneBorderSize = 2 * PaneBorderSize; static const int AnimationDurationInMilliseconds = 200; static const Duration AnimationDuration = DurationHelper::FromTimeSpan(winrt::Windows::Foundation::TimeSpan(std::chrono::milliseconds(AnimationDurationInMilliseconds))); -Pane::Pane(const Profile& profile, const TermControl& control, const bool lastFocused) : - _control{ control }, - _lastActive{ lastFocused }, - _profile{ profile } +Pane::Pane(const IPaneContent& content, const bool lastFocused) : + _content{ content }, + _lastActive{ lastFocused } { _root.Children().Append(_borderFirst); - _borderFirst.Child(_control); - _setupControlEvents(); + const auto& control{ _content.GetRoot() }; + _borderFirst.Child(control); // Register an event with the control to have it inform us when it gains focus. - _gotFocusRevoker = _control.GotFocus(winrt::auto_revoke, { this, &Pane::_ControlGotFocusHandler }); - _lostFocusRevoker = _control.LostFocus(winrt::auto_revoke, { this, &Pane::_ControlLostFocusHandler }); + if (control) + { + _gotFocusRevoker = control.GotFocus(winrt::auto_revoke, { this, &Pane::_ContentGotFocusHandler }); + _lostFocusRevoker = control.LostFocus(winrt::auto_revoke, { this, &Pane::_ContentLostFocusHandler }); + } // When our border is tapped, make sure to transfer focus to our control. // LOAD-BEARING: This will NOT work if the border's BorderBrush is set to @@ -102,19 +105,6 @@ Pane::Pane(std::shared_ptr first, }); } -void Pane::_setupControlEvents() -{ - _controlEvents._ConnectionStateChanged = _control.ConnectionStateChanged(winrt::auto_revoke, { this, &Pane::_ControlConnectionStateChangedHandler }); - _controlEvents._WarningBell = _control.WarningBell(winrt::auto_revoke, { this, &Pane::_ControlWarningBellHandler }); - _controlEvents._CloseTerminalRequested = _control.CloseTerminalRequested(winrt::auto_revoke, { this, &Pane::_CloseTerminalRequestedHandler }); - _controlEvents._RestartTerminalRequested = _control.RestartTerminalRequested(winrt::auto_revoke, { this, &Pane::_RestartTerminalRequestedHandler }); - _controlEvents._ReadOnlyChanged = _control.ReadOnlyChanged(winrt::auto_revoke, { this, &Pane::_ControlReadOnlyChangedHandler }); -} -void Pane::_removeControlEvents() -{ - _controlEvents = {}; -} - // Method Description: // - Extract the terminal settings from the current (leaf) pane's control // to be used to create an equivalent control @@ -129,55 +119,7 @@ NewTerminalArgs Pane::GetTerminalArgsForPane(const bool asContent) const // Leaves are the only things that have controls assert(_IsLeaf()); - NewTerminalArgs args{}; - auto controlSettings = _control.Settings(); - - args.Profile(controlSettings.ProfileName()); - // If we know the user's working directory use it instead of the profile. - if (const auto dir = _control.WorkingDirectory(); !dir.empty()) - { - args.StartingDirectory(dir); - } - else - { - args.StartingDirectory(controlSettings.StartingDirectory()); - } - args.TabTitle(controlSettings.StartingTitle()); - args.Commandline(controlSettings.Commandline()); - args.SuppressApplicationTitle(controlSettings.SuppressApplicationTitle()); - if (controlSettings.TabColor() || controlSettings.StartingTabColor()) - { - til::color c; - // StartingTabColor is prioritized over other colors - if (const auto color = controlSettings.StartingTabColor()) - { - c = til::color(color.Value()); - } - else - { - c = til::color(controlSettings.TabColor().Value()); - } - - args.TabColor(winrt::Windows::Foundation::IReference{ static_cast(c) }); - } - - // TODO:GH#9800 - we used to be able to persist the color scheme that a - // TermControl was initialized with, by name. With the change to having the - // control own its own copy of its settings, this isn't possible anymore. - // - // We may be able to get around this by storing the Name in the Core::Scheme - // object. That would work for schemes set by the Terminal, but not ones set - // by VT, but that seems good enough. - - // Only fill in the ContentId if absolutely needed. If you fill in a number - // here (even 0), we'll serialize that number, AND treat that action as an - // "attach existing" rather than a "create" - if (asContent) - { - args.ContentId(_control.ContentId()); - } - - return args; + return _content.GetNewTerminalArgs(asContent); } // Method Description: @@ -1022,181 +964,18 @@ Pane::PaneNeighborSearch Pane::_FindPaneAndNeighbor(const std::shared_ptr } // Method Description: -// - Called when our attached control is closed. Triggers listeners to our close -// event, if we're a leaf pane. -// - If this was called, and we became a parent pane (due to work on another -// thread), this function will do nothing (allowing the control's new parent -// to handle the event instead). +// - Returns true if the connection state of this pane is closed. // Arguments: // - // Return Value: -// - -winrt::fire_and_forget Pane::_ControlConnectionStateChangedHandler(const winrt::Windows::Foundation::IInspectable& sender, - const winrt::Windows::Foundation::IInspectable& /*args*/) -{ - auto newConnectionState = ConnectionState::Closed; - if (const auto coreState = sender.try_as()) - { - newConnectionState = coreState.ConnectionState(); - } - - const auto previousConnectionState = std::exchange(_connectionState, newConnectionState); - if (newConnectionState < ConnectionState::Closed) - { - // Pane doesn't care if the connection isn't entering a terminal state. - co_return; - } - - const auto weakThis = weak_from_this(); - co_await wil::resume_foreground(_root.Dispatcher()); - const auto strongThis = weakThis.lock(); - if (!strongThis) - { - co_return; - } - - // It's possible that this event handler started being executed, scheduled - // on the UI thread, another child got created. So our control is - // actually no longer _our_ control, and instead could be a descendant. - // - // When the control's new Pane takes ownership of the control, the new - // parent will register its own event handler. That event handler will get - // fired after this handler returns, and will properly cleanup state. - if (!_IsLeaf()) - { - co_return; - } - - if (previousConnectionState < ConnectionState::Connected && newConnectionState >= ConnectionState::Failed) - { - // A failure to complete the connection (before it has _connected_) is not covered by "closeOnExit". - // This is to prevent a misconfiguration (closeOnExit: always, startingDirectory: garbage) resulting - // in Terminal flashing open and immediately closed. - co_return; - } - - if (_profile) - { - if (_isDefTermSession && _profile.CloseOnExit() == CloseOnExitMode::Automatic) - { - // For 'automatic', we only care about the connection state if we were launched by Terminal - // Since we were launched via defterm, ignore the connection state (i.e. we treat the - // close on exit mode as 'always', see GH #13325 for discussion) - Close(); - } - - const auto mode = _profile.CloseOnExit(); - if ((mode == CloseOnExitMode::Always) || - ((mode == CloseOnExitMode::Graceful || mode == CloseOnExitMode::Automatic) && newConnectionState == ConnectionState::Closed)) - { - Close(); - } - } -} - -void Pane::_CloseTerminalRequestedHandler(const winrt::Windows::Foundation::IInspectable& /*sender*/, - const winrt::Windows::Foundation::IInspectable& /*args*/) -{ - // It's possible that this event handler started being executed, then before - // we got the lock, another thread created another child. So our control is - // actually no longer _our_ control, and instead could be a descendant. - // - // When the control's new Pane takes ownership of the control, the new - // parent will register its own event handler. That event handler will get - // fired after this handler returns, and will properly cleanup state. - if (!_IsLeaf()) - { - return; - } - - Close(); -} - -void Pane::_RestartTerminalRequestedHandler(const winrt::Windows::Foundation::IInspectable& /*sender*/, - const winrt::Windows::Foundation::IInspectable& /*args*/) -{ - if (!_IsLeaf()) - { - return; - } - RestartTerminalRequested.raise(shared_from_this()); -} - -winrt::fire_and_forget Pane::_playBellSound(winrt::Windows::Foundation::Uri uri) -{ - auto weakThis{ weak_from_this() }; - - co_await wil::resume_foreground(_root.Dispatcher()); - if (auto pane{ weakThis.lock() }) - { - if (!_bellPlayerCreated) - { - // The MediaPlayer might not exist on Windows N SKU. - try - { - _bellPlayerCreated = true; - _bellPlayer = winrt::Windows::Media::Playback::MediaPlayer(); - // GH#12258: The media keys (like play/pause) should have no effect on our bell sound. - _bellPlayer.CommandManager().IsEnabled(false); - } - CATCH_LOG(); - } - if (_bellPlayer) - { - const auto source{ winrt::Windows::Media::Core::MediaSource::CreateFromUri(uri) }; - const auto item{ winrt::Windows::Media::Playback::MediaPlaybackItem(source) }; - _bellPlayer.Source(item); - _bellPlayer.Play(); - } - } -} - -// Method Description: -// - Plays a warning note when triggered by the BEL control character, -// using the sound configured for the "Critical Stop" system event.` -// This matches the behavior of the Windows Console host. -// - Will also flash the taskbar if the bellStyle setting for this profile -// has the 'visual' flag set -// Arguments: -// - -void Pane::_ControlWarningBellHandler(const winrt::Windows::Foundation::IInspectable& /*sender*/, - const winrt::Windows::Foundation::IInspectable& /*eventArgs*/) +// - true if the connection state of this Pane is closed. +bool Pane::IsConnectionClosed() const { - if (!_IsLeaf()) - { - return; - } - if (_profile) + if (const auto& control{ GetTerminalControl() }) { - // We don't want to do anything if nothing is set, so check for that first - if (static_cast(_profile.BellStyle()) != 0) - { - if (WI_IsFlagSet(_profile.BellStyle(), winrt::Microsoft::Terminal::Settings::Model::BellStyle::Audible)) - { - // Audible is set, play the sound - auto sounds{ _profile.BellSound() }; - if (sounds && sounds.Size() > 0) - { - winrt::hstring soundPath{ wil::ExpandEnvironmentStringsW(sounds.GetAt(rand() % sounds.Size()).c_str()) }; - winrt::Windows::Foundation::Uri uri{ soundPath }; - _playBellSound(uri); - } - else - { - const auto soundAlias = reinterpret_cast(SND_ALIAS_SYSTEMHAND); - PlaySound(soundAlias, NULL, SND_ALIAS_ID | SND_ASYNC | SND_SENTRY); - } - } - - if (WI_IsFlagSet(_profile.BellStyle(), winrt::Microsoft::Terminal::Settings::Model::BellStyle::Window)) - { - _control.BellLightOn(); - } - - // raise the event with the bool value corresponding to the taskbar flag - PaneRaiseBell.raise(nullptr, WI_IsFlagSet(_profile.BellStyle(), winrt::Microsoft::Terminal::Settings::Model::BellStyle::Taskbar)); - } + return control.ConnectionState() >= ConnectionState::Closed; } + return false; } // Event Description: @@ -1207,7 +986,7 @@ void Pane::_ControlWarningBellHandler(const winrt::Windows::Foundation::IInspect // - // Return Value: // - -void Pane::_ControlGotFocusHandler(const winrt::Windows::Foundation::IInspectable& sender, +void Pane::_ContentGotFocusHandler(const winrt::Windows::Foundation::IInspectable& sender, const RoutedEventArgs& /* args */) { auto f = FocusState::Programmatic; @@ -1222,7 +1001,7 @@ void Pane::_ControlGotFocusHandler(const winrt::Windows::Foundation::IInspectabl // - Called when our control loses focus. We'll use this to trigger our LostFocus // callback. The tab that's hosting us should have registered a callback which // can be used to update its own internal focus state -void Pane::_ControlLostFocusHandler(const winrt::Windows::Foundation::IInspectable& /* sender */, +void Pane::_ContentLostFocusHandler(const winrt::Windows::Foundation::IInspectable& /* sender */, const RoutedEventArgs& /* args */) { LostFocus.raise(shared_from_this()); @@ -1245,21 +1024,9 @@ void Pane::Close() // and connections beneath it. void Pane::Shutdown() { - // Clear out our media player callbacks, and stop any playing media. This - // will prevent the callback from being triggered after we've closed, and - // also make sure that our sound stops when we're closed. - if (_bellPlayer) - { - _bellPlayer.Pause(); - _bellPlayer.Source(nullptr); - _bellPlayer.Close(); - _bellPlayer = nullptr; - _bellPlayerCreated = false; - } - if (_IsLeaf()) { - _control.Close(); + _content.Close(); } else { @@ -1314,7 +1081,7 @@ TermControl Pane::GetLastFocusedTerminalControl() { if (p->_IsLeaf()) { - return p->_control; + return p->GetTerminalControl(); } pane = p; } @@ -1322,7 +1089,8 @@ TermControl Pane::GetLastFocusedTerminalControl() } return _firstChild->GetLastFocusedTerminalControl(); } - return _control; + // we _are_ a leaf. + return GetTerminalControl(); } // Method Description: @@ -1332,9 +1100,16 @@ TermControl Pane::GetLastFocusedTerminalControl() // - // Return Value: // - nullptr if this Pane is a parent, otherwise the TermControl of this Pane. -TermControl Pane::GetTerminalControl() +TermControl Pane::GetTerminalControl() const { - return _IsLeaf() ? _control : nullptr; + if (const auto& terminalPane{ _getTerminalContent() }) + { + return terminalPane.GetTermControl(); + } + else + { + return nullptr; + } } // Method Description: @@ -1381,19 +1156,11 @@ void Pane::SetActive() Profile Pane::GetFocusedProfile() { auto lastFocused = GetActivePane(); - return lastFocused ? lastFocused->_profile : nullptr; -} - -// Method Description: -// - Returns true if the connection state of this pane is closed. If this Pane is not a leaf this will -// return false. -// Arguments: -// - -// Return Value: -// - true if the connection state of this Pane is closed. -bool Pane::IsConnectionClosed() const -{ - return _control && _control.ConnectionState() >= ConnectionState::Closed; + if (const auto& terminalPane{ lastFocused->_getTerminalContent() }) + { + return terminalPane.GetProfile(); + } + return nullptr; } // Method Description: @@ -1466,10 +1233,7 @@ void Pane::UpdateVisuals() void Pane::_Focus() { GotFocus.raise(shared_from_this(), FocusState::Programmatic); - if (const auto& control = GetLastFocusedTerminalControl()) - { - control.Focus(FocusState::Programmatic); - } + _content.Focus(FocusState::Programmatic); } // Method Description: @@ -1519,9 +1283,10 @@ void Pane::UpdateSettings(const TerminalSettingsCreateResult& settings, const Pr { assert(_IsLeaf()); - _profile = profile; - - _control.UpdateControlSettings(settings.DefaultSettings(), settings.UnfocusedSettings()); + if (const auto& terminalPane{ _getTerminalContent() }) + { + return terminalPane.UpdateSettings(settings, profile); + } } // Method Description: @@ -1573,7 +1338,7 @@ std::shared_ptr Pane::DetachPane(std::shared_ptr pane) auto detached = isFirstChild ? _firstChild : _secondChild; // Remove the child from the tree, replace the current node with the // other child. - _CloseChild(isFirstChild, true); + _CloseChild(isFirstChild); // Update the borders on this pane and any children to match if we have // no parent. @@ -1602,12 +1367,9 @@ std::shared_ptr Pane::DetachPane(std::shared_ptr pane) // Arguments: // - closeFirst: if true, the first child should be closed, and the second // should be preserved, and vice-versa for false. -// - isDetaching: if true, then the pane event handlers for the closed child -// should be kept, this way they don't have to be recreated when it is later -// reattached to a tree somewhere as the control moves with the pane. // Return Value: // - -void Pane::_CloseChild(const bool closeFirst, const bool isDetaching) +void Pane::_CloseChild(const bool closeFirst) { // If we're a leaf, then chances are both our children closed in close // succession. We waited on the lock while the other child was closed, so @@ -1643,35 +1405,16 @@ void Pane::_CloseChild(const bool closeFirst, const bool isDetaching) _borders = _GetCommonBorders(); // take the control, profile, id and isDefTermSession of the pane that _wasn't_ closed. - _control = remainingChild->_control; - _connectionState = remainingChild->_connectionState; - _profile = remainingChild->_profile; + _content = remainingChild->_content; _id = remainingChild->Id(); - _isDefTermSession = remainingChild->_isDefTermSession; - - // Add our new event handler before revoking the old one. - _setupControlEvents(); // Revoke the old event handlers. Remove both the handlers for the panes // themselves closing, and remove their handlers for their controls // closing. At this point, if the remaining child's control is closed, // they'll trigger only our event handler for the control's close. - // However, if we are detaching the pane we want to keep its control - // handlers since it is just getting moved. - if (!isDetaching) - { - closedChild->WalkTree([](auto p) { - if (p->_IsLeaf()) - { - p->_removeControlEvents(); - } - }); - } - closedChild->Closed(closedChildClosedToken); remainingChild->Closed(remainingChildClosedToken); - remainingChild->_removeControlEvents(); // If we or either of our children was focused, we want to take that // focus from them. @@ -1691,7 +1434,8 @@ void Pane::_CloseChild(const bool closeFirst, const bool isDetaching) // Reattach the TermControl to our grid. _root.Children().Append(_borderFirst); - _borderFirst.Child(_control); + const auto& control{ _content.GetRoot() }; + _borderFirst.Child(control); // Make sure to set our _splitState before focusing the control. If you // fail to do this, when the tab handles the GotFocus event and asks us @@ -1700,14 +1444,17 @@ void Pane::_CloseChild(const bool closeFirst, const bool isDetaching) _splitState = SplitState::None; // re-attach our handler for the control's GotFocus event. - _gotFocusRevoker = _control.GotFocus(winrt::auto_revoke, { this, &Pane::_ControlGotFocusHandler }); - _lostFocusRevoker = _control.LostFocus(winrt::auto_revoke, { this, &Pane::_ControlLostFocusHandler }); + if (control) + { + _gotFocusRevoker = control.GotFocus(winrt::auto_revoke, { this, &Pane::_ContentGotFocusHandler }); + _lostFocusRevoker = control.LostFocus(winrt::auto_revoke, { this, &Pane::_ContentLostFocusHandler }); + } // If we're inheriting the "last active" state from one of our children, // focus our control now. This should trigger our own GotFocus event. if (usedToFocusClosedChildsTerminal || _lastActive) { - _control.Focus(FocusState::Programmatic); + _content.Focus(FocusState::Programmatic); // See GH#7252 // Manually fire off the GotFocus event. Typically, this is done @@ -1746,15 +1493,6 @@ void Pane::_CloseChild(const bool closeFirst, const bool isDetaching) // Remove the event handlers on the old children remainingChild->Closed(remainingChildClosedToken); closedChild->Closed(closedChildClosedToken); - if (!isDetaching) - { - closedChild->WalkTree([](auto p) { - if (p->_IsLeaf()) - { - p->_removeControlEvents(); - } - }); - } // Reset our UI: _root.Children().Clear(); @@ -1847,7 +1585,7 @@ void Pane::_CloseChildRoutine(const bool closeFirst) // this one doesn't seem to. if (!animationsEnabledInOS || !animationsEnabledInApp || eitherChildZoomed) { - _CloseChild(closeFirst, false); + _CloseChild(closeFirst); return; } @@ -1950,7 +1688,7 @@ void Pane::_CloseChildRoutine(const bool closeFirst) { // We don't need to manually undo any of the above trickiness. // We're going to re-parent the child's content into us anyways - pane->_CloseChild(closeFirst, false); + pane->_CloseChild(closeFirst); } }); } @@ -2173,7 +1911,7 @@ void Pane::_SetupEntranceAnimation() auto child = isFirstChild ? _firstChild : _secondChild; auto childGrid = child->_root; // If we are splitting a parent pane this may be null - auto control = child->_control; + auto control = child->_content.GetRoot(); // Build up our animation: // * it'll take as long as our duration (200ms) // * it'll change the value of our property from 0 to secondSize @@ -2494,9 +2232,6 @@ std::pair, std::shared_ptr> Pane::_Split(SplitDirect if (_IsLeaf()) { - // revoke our handler - the child will take care of the control now. - _removeControlEvents(); - // Remove our old GotFocus handler from the control. We don't want the // control telling us that it's now focused, we want it telling its new // parent. @@ -2525,11 +2260,8 @@ std::pair, std::shared_ptr> Pane::_Split(SplitDirect else { // Move our control, guid, isDefTermSession into the first one. - _firstChild = std::make_shared(_profile, _control); - _firstChild->_connectionState = std::exchange(_connectionState, ConnectionState::NotConnected); - _profile = nullptr; - _control = { nullptr }; - _firstChild->_isDefTermSession = _isDefTermSession; + _firstChild = std::make_shared(_content); + _content = nullptr; _firstChild->_broadcastEnabled = _broadcastEnabled; } @@ -2852,8 +2584,16 @@ float Pane::CalcSnappedDimension(const bool widthOrHeight, const float dimension // If requested size is already snapped, then both returned values equal this value. Pane::SnapSizeResult Pane::_CalcSnappedDimension(const bool widthOrHeight, const float dimension) const { + const auto direction{ widthOrHeight ? PaneSnapDirection::Width : PaneSnapDirection::Height }; + if (_IsLeaf()) { + const auto& snappable{ _content.try_as() }; + if (!snappable) + { + return { dimension, dimension }; + } + // If we're a leaf pane, align to the grid of controlling terminal const auto minSize = _GetMinSize(); @@ -2864,8 +2604,10 @@ Pane::SnapSizeResult Pane::_CalcSnappedDimension(const bool widthOrHeight, const return { minDimension, minDimension }; } - auto lower = _control.SnapDimensionToGrid(widthOrHeight, dimension); - if (widthOrHeight) + auto lower = snappable.SnapDownToGrid(widthOrHeight ? PaneSnapDirection::Width : PaneSnapDirection::Height, + dimension); + + if (direction == PaneSnapDirection::Width) { lower += WI_IsFlagSet(_borders, Borders::Left) ? PaneBorderSize : 0; lower += WI_IsFlagSet(_borders, Borders::Right) ? PaneBorderSize : 0; @@ -2884,8 +2626,10 @@ Pane::SnapSizeResult Pane::_CalcSnappedDimension(const bool widthOrHeight, const } else { - const auto cellSize = _control.CharacterDimensions(); - const auto higher = lower + (widthOrHeight ? cellSize.Width : cellSize.Height); + const auto cellSize = snappable.GridUnitSize(); + const auto higher = lower + (direction == PaneSnapDirection::Width ? + cellSize.Width : + cellSize.Height); return { lower, higher }; } } @@ -2931,21 +2675,34 @@ void Pane::_AdvanceSnappedDimension(const bool widthOrHeight, LayoutSizeNode& si { if (_IsLeaf()) { - // We're a leaf pane, so just add one more row or column (unless isMinimumSize - // is true, see below). - - if (sizeNode.isMinimumSize) + const auto& snappable{ _content.try_as() }; + if (snappable) { - // If the node is of its minimum size, this size might not be snapped (it might - // be, say, half a character, or fixed 10 pixels), so snap it upward. It might - // however be already snapped, so add 1 to make sure it really increases - // (not strictly necessary but to avoid surprises). - sizeNode.size = _CalcSnappedDimension(widthOrHeight, sizeNode.size + 1).higher; + // We're a leaf pane, so just add one more row or column (unless isMinimumSize + // is true, see below). + + if (sizeNode.isMinimumSize) + { + // If the node is of its minimum size, this size might not be snapped (it might + // be, say, half a character, or fixed 10 pixels), so snap it upward. It might + // however be already snapped, so add 1 to make sure it really increases + // (not strictly necessary but to avoid surprises). + sizeNode.size = _CalcSnappedDimension(widthOrHeight, sizeNode.size + 1).higher; + } + else + { + const auto cellSize = snappable.GridUnitSize(); + sizeNode.size += widthOrHeight ? cellSize.Width : cellSize.Height; + } } else { - const auto cellSize = _control.CharacterDimensions(); - sizeNode.size += widthOrHeight ? cellSize.Width : cellSize.Height; + // If we're a leaf that didn't have a TermControl, then just increment + // by one. We have to increment by _some_ value, because this is used in + // a while() loop to find the next bigger size we can snap to. But since + // a non-terminal control doesn't really care what size it's snapped to, + // we can just say "one pixel larger is the next snap point" + sizeNode.size += 1; } } else @@ -3050,7 +2807,7 @@ Size Pane::_GetMinSize() const { if (_IsLeaf()) { - auto controlSize = _control.MinimumSize(); + auto controlSize = _content.MinimumSize(); auto newWidth = controlSize.Width; auto newHeight = controlSize.Height; @@ -3148,14 +2905,17 @@ int Pane::GetLeafPaneCount() const noexcept // created via default handoff void Pane::FinalizeConfigurationGivenDefault() { - _isDefTermSession = true; + if (const auto& terminalPane{ _content.try_as() }) + { + terminalPane.MarkAsDefterm(); + } } // Method Description: // - Returns true if the pane or one of its descendants is read-only bool Pane::ContainsReadOnly() const { - return _IsLeaf() ? _control.ReadOnly() : (_firstChild->ContainsReadOnly() || _secondChild->ContainsReadOnly()); + return _IsLeaf() ? _content.ReadOnly() : (_firstChild->ContainsReadOnly() || _secondChild->ContainsReadOnly()); } // Method Description: @@ -3170,8 +2930,8 @@ void Pane::CollectTaskbarStates(std::vector& s { if (_IsLeaf()) { - auto tbState{ winrt::make(_control.TaskbarState(), - _control.TaskbarProgress()) }; + auto tbState{ winrt::make(_content.TaskbarState(), + _content.TaskbarProgress()) }; states.push_back(tbState); } else @@ -3186,9 +2946,12 @@ void Pane::EnableBroadcast(bool enabled) if (_IsLeaf()) { _broadcastEnabled = enabled; - _control.CursorVisibility(enabled ? - CursorDisplayState::Shown : - CursorDisplayState::Default); + if (const auto& termControl{ GetTerminalControl() }) + { + termControl.CursorVisibility(enabled ? + CursorDisplayState::Shown : + CursorDisplayState::Default); + } UpdateVisuals(); } else @@ -3205,9 +2968,12 @@ void Pane::BroadcastKey(const winrt::Microsoft::Terminal::Control::TermControl& const bool keyDown) { WalkTree([&](const auto& pane) { - if (pane->_IsLeaf() && pane->_control != sourceControl && !pane->_control.ReadOnly()) + if (const auto& termControl{ pane->GetTerminalControl() }) { - pane->_control.RawWriteKeyEvent(vkey, scanCode, modifiers, keyDown); + if (termControl != sourceControl && !termControl.ReadOnly()) + { + termControl.RawWriteKeyEvent(vkey, scanCode, modifiers, keyDown); + } } }); } @@ -3218,9 +2984,12 @@ void Pane::BroadcastChar(const winrt::Microsoft::Terminal::Control::TermControl& const winrt::Microsoft::Terminal::Core::ControlKeyStates modifiers) { WalkTree([&](const auto& pane) { - if (pane->_IsLeaf() && pane->_control != sourceControl && !pane->_control.ReadOnly()) + if (const auto& termControl{ pane->GetTerminalControl() }) { - pane->_control.RawWriteChar(character, scanCode, modifiers); + if (termControl != sourceControl && !termControl.ReadOnly()) + { + termControl.RawWriteChar(character, scanCode, modifiers); + } } }); } @@ -3229,19 +2998,16 @@ void Pane::BroadcastString(const winrt::Microsoft::Terminal::Control::TermContro const winrt::hstring& text) { WalkTree([&](const auto& pane) { - if (pane->_IsLeaf() && pane->_control != sourceControl && !pane->_control.ReadOnly()) + if (const auto& termControl{ pane->GetTerminalControl() }) { - pane->_control.RawWriteString(text); + if (termControl != sourceControl && !termControl.ReadOnly()) + { + termControl.RawWriteString(text); + } } }); } -void Pane::_ControlReadOnlyChangedHandler(const winrt::Windows::Foundation::IInspectable& /*sender*/, - const winrt::Windows::Foundation::IInspectable& /*e*/) -{ - UpdateVisuals(); -} - winrt::Windows::UI::Xaml::Media::SolidColorBrush Pane::_ComputeBorderColor() { if (_lastActive) @@ -3249,7 +3015,7 @@ winrt::Windows::UI::Xaml::Media::SolidColorBrush Pane::_ComputeBorderColor() return _themeResources.focusedBorderBrush; } - if (_broadcastEnabled && (_IsLeaf() && !_control.ReadOnly())) + if (_broadcastEnabled && (_IsLeaf() && !_content.ReadOnly())) { return _themeResources.broadcastBorderBrush; } diff --git a/src/cascadia/TerminalApp/Pane.h b/src/cascadia/TerminalApp/Pane.h index e7660d4b70c..1f237f83190 100644 --- a/src/cascadia/TerminalApp/Pane.h +++ b/src/cascadia/TerminalApp/Pane.h @@ -21,6 +21,7 @@ #pragma once #include "TaskbarState.h" +#include "TerminalPaneContent.h" // fwdecl unittest classes namespace TerminalAppLocalTests @@ -61,8 +62,7 @@ struct PaneResources class Pane : public std::enable_shared_from_this { public: - Pane(const winrt::Microsoft::Terminal::Settings::Model::Profile& profile, - const winrt::Microsoft::Terminal::Control::TermControl& control, + Pane(const winrt::TerminalApp::IPaneContent& content, const bool lastFocused = false); Pane(std::shared_ptr first, @@ -73,7 +73,7 @@ class Pane : public std::enable_shared_from_this std::shared_ptr GetActivePane(); winrt::Microsoft::Terminal::Control::TermControl GetLastFocusedTerminalControl(); - winrt::Microsoft::Terminal::Control::TermControl GetTerminalControl(); + winrt::Microsoft::Terminal::Control::TermControl GetTerminalControl() const; winrt::Microsoft::Terminal::Settings::Model::Profile GetFocusedProfile(); bool IsConnectionClosed() const; @@ -82,10 +82,15 @@ class Pane : public std::enable_shared_from_this // - If this is a branch/root pane, return nullptr. winrt::Microsoft::Terminal::Settings::Model::Profile GetProfile() const { - return _profile; + if (const auto& c{ _content.try_as() }) + { + return c.GetProfile(); + } + return nullptr; } winrt::Windows::UI::Xaml::Controls::Grid GetRootElement(); + winrt::TerminalApp::IPaneContent GetContent() const noexcept { return _IsLeaf() ? _content : nullptr; } bool WasLastFocused() const noexcept; void UpdateVisuals(); @@ -217,9 +222,7 @@ class Pane : public std::enable_shared_from_this til::event GotFocus; til::event>> LostFocus; - til::event> PaneRaiseBell; til::event>> Detached; - til::event>> RestartTerminalRequested; private: struct PanePoint; @@ -239,10 +242,8 @@ class Pane : public std::enable_shared_from_this std::shared_ptr _secondChild{ nullptr }; SplitState _splitState{ SplitState::None }; float _desiredSplitPosition; - winrt::Microsoft::Terminal::Control::TermControl _control{ nullptr }; - winrt::Microsoft::Terminal::TerminalConnection::ConnectionState _connectionState{ winrt::Microsoft::Terminal::TerminalConnection::ConnectionState::NotConnected }; - winrt::Microsoft::Terminal::Settings::Model::Profile _profile{ nullptr }; - bool _isDefTermSession{ false }; + + winrt::TerminalApp::IPaneContent _content{ nullptr }; #pragma endregion std::optional _id; @@ -252,17 +253,6 @@ class Pane : public std::enable_shared_from_this winrt::event_token _firstClosedToken{ 0 }; winrt::event_token _secondClosedToken{ 0 }; - struct ControlEventTokens - { - winrt::Microsoft::Terminal::Control::TermControl::ConnectionStateChanged_revoker _ConnectionStateChanged; - winrt::Microsoft::Terminal::Control::TermControl::WarningBell_revoker _WarningBell; - winrt::Microsoft::Terminal::Control::TermControl::CloseTerminalRequested_revoker _CloseTerminalRequested; - winrt::Microsoft::Terminal::Control::TermControl::RestartTerminalRequested_revoker _RestartTerminalRequested; - winrt::Microsoft::Terminal::Control::TermControl::ReadOnlyChanged_revoker _ReadOnlyChanged; - } _controlEvents; - void _setupControlEvents(); - void _removeControlEvents(); - winrt::Windows::UI::Xaml::UIElement::GotFocus_revoker _gotFocusRevoker; winrt::Windows::UI::Xaml::UIElement::LostFocus_revoker _lostFocusRevoker; @@ -271,13 +261,14 @@ class Pane : public std::enable_shared_from_this bool _zoomed{ false }; bool _broadcastEnabled{ false }; - winrt::Windows::Media::Playback::MediaPlayer _bellPlayer{ nullptr }; - bool _bellPlayerCreated{ false }; - bool _IsLeaf() const noexcept; bool _HasFocusedChild() const noexcept; void _SetupChildCloseHandlers(); bool _HasChild(const std::shared_ptr child); + winrt::TerminalApp::TerminalPaneContent _getTerminalContent() const + { + return _IsLeaf() ? _content.try_as() : nullptr; + } std::pair, std::shared_ptr> _Split(winrt::Microsoft::Terminal::Settings::Model::SplitDirection splitType, const float splitSize, @@ -303,24 +294,16 @@ class Pane : public std::enable_shared_from_this const winrt::Microsoft::Terminal::Settings::Model::FocusDirection& direction, const PanePoint offset); - void _CloseChild(const bool closeFirst, const bool isDetaching); + void _CloseChild(const bool closeFirst); void _CloseChildRoutine(const bool closeFirst); void _Focus(); void _FocusFirstChild(); - winrt::fire_and_forget _ControlConnectionStateChangedHandler(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Windows::Foundation::IInspectable& /*args*/); - void _ControlWarningBellHandler(const winrt::Windows::Foundation::IInspectable& sender, - const winrt::Windows::Foundation::IInspectable& e); - void _ControlGotFocusHandler(const winrt::Windows::Foundation::IInspectable& sender, + void _ContentGotFocusHandler(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Windows::UI::Xaml::RoutedEventArgs& e); - void _ControlLostFocusHandler(const winrt::Windows::Foundation::IInspectable& sender, + void _ContentLostFocusHandler(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Windows::UI::Xaml::RoutedEventArgs& e); - void _ControlReadOnlyChangedHandler(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Windows::Foundation::IInspectable& e); - - void _CloseTerminalRequestedHandler(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Windows::Foundation::IInspectable& /*args*/); - void _RestartTerminalRequestedHandler(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Windows::Foundation::IInspectable& /*args*/); - std::pair _CalcChildrenSizes(const float fullSize) const; SnapChildrenSizeResult _CalcSnappedChildrenSizes(const bool widthOrHeight, const float fullSize) const; SnapSizeResult _CalcSnappedDimension(const bool widthOrHeight, const float dimension) const; @@ -331,8 +314,6 @@ class Pane : public std::enable_shared_from_this SplitState _convertAutomaticOrDirectionalSplitState(const winrt::Microsoft::Terminal::Settings::Model::SplitDirection& splitType) const; - winrt::fire_and_forget _playBellSound(winrt::Windows::Foundation::Uri uri); - // Function Description: // - Returns true if the given direction can be used with the given split // type. diff --git a/src/cascadia/TerminalApp/ScratchpadContent.cpp b/src/cascadia/TerminalApp/ScratchpadContent.cpp new file mode 100644 index 00000000000..dcb13697b54 --- /dev/null +++ b/src/cascadia/TerminalApp/ScratchpadContent.cpp @@ -0,0 +1,50 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#include "pch.h" +#include "ScratchpadContent.h" + +using namespace winrt::Windows::Foundation; +using namespace winrt::Windows::UI::Xaml; +using namespace winrt::Microsoft::Terminal::Settings::Model; + +namespace winrt::TerminalApp::implementation +{ + ScratchpadContent::ScratchpadContent() + { + _root = winrt::Windows::UI::Xaml::Controls::Grid{}; + // Vertical and HorizontalAlignment are Stretch by default + + auto res = Windows::UI::Xaml::Application::Current().Resources(); + auto bg = res.Lookup(winrt::box_value(L"UnfocusedBorderBrush")); + _root.Background(bg.try_as()); + + _box = winrt::Windows::UI::Xaml::Controls::TextBox{}; + _box.Margin({ 10, 10, 10, 10 }); + _box.AcceptsReturn(true); + _box.TextWrapping(TextWrapping::Wrap); + _root.Children().Append(_box); + } + + winrt::Windows::UI::Xaml::FrameworkElement ScratchpadContent::GetRoot() + { + return _root; + } + winrt::Windows::Foundation::Size ScratchpadContent::MinimumSize() + { + return { 1, 1 }; + } + void ScratchpadContent::Focus(winrt::Windows::UI::Xaml::FocusState reason) + { + _box.Focus(reason); + } + void ScratchpadContent::Close() + { + CloseRequested.raise(*this, nullptr); + } + + NewTerminalArgs ScratchpadContent::GetNewTerminalArgs(const bool /* asContent */) const + { + return nullptr; + } +} diff --git a/src/cascadia/TerminalApp/ScratchpadContent.h b/src/cascadia/TerminalApp/ScratchpadContent.h new file mode 100644 index 00000000000..10aa36b6b85 --- /dev/null +++ b/src/cascadia/TerminalApp/ScratchpadContent.h @@ -0,0 +1,40 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#pragma once +#include "winrt/TerminalApp.h" + +namespace winrt::TerminalApp::implementation +{ + class ScratchpadContent : public winrt::implements + { + public: + ScratchpadContent(); + + winrt::Windows::UI::Xaml::FrameworkElement GetRoot(); + + winrt::Windows::Foundation::Size MinimumSize(); + + void Focus(winrt::Windows::UI::Xaml::FocusState reason = winrt::Windows::UI::Xaml::FocusState::Programmatic); + void Close(); + winrt::Microsoft::Terminal::Settings::Model::NewTerminalArgs GetNewTerminalArgs(const bool asContent) const; + + winrt::hstring Title() { return L"Scratchpad"; } + uint64_t TaskbarState() { return 0; } + uint64_t TaskbarProgress() { return 0; } + bool ReadOnly() { return false; } + + til::typed_event<> ConnectionStateChanged; + til::typed_event CloseRequested; + til::typed_event BellRequested; + til::typed_event TitleChanged; + til::typed_event TabColorChanged; + til::typed_event TaskbarProgressChanged; + til::typed_event ReadOnlyChanged; + til::typed_event FocusRequested; + + private: + winrt::Windows::UI::Xaml::Controls::Grid _root{ nullptr }; + winrt::Windows::UI::Xaml::Controls::TextBox _box{ nullptr }; + }; +} diff --git a/src/cascadia/TerminalApp/TabManagement.cpp b/src/cascadia/TerminalApp/TabManagement.cpp index 2228f8234e9..b407c6107c1 100644 --- a/src/cascadia/TerminalApp/TabManagement.cpp +++ b/src/cascadia/TerminalApp/TabManagement.cpp @@ -63,7 +63,7 @@ namespace winrt::TerminalApp::implementation // - existingConnection: An optional connection that is already established to a PTY // for this tab to host instead of creating one. // If not defined, the tab will create the connection. - HRESULT TerminalPage::_OpenNewTab(const NewTerminalArgs& newTerminalArgs, winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection existingConnection) + HRESULT TerminalPage::_OpenNewTab(const NewTerminalArgs& newTerminalArgs) try { const auto profile{ _settings.GetProfileForArgs(newTerminalArgs) }; @@ -86,7 +86,7 @@ namespace winrt::TerminalApp::implementation // // This call to _MakePane won't return nullptr, we already checked that // case above with the _maybeElevate call. - _CreateNewTabFromPane(_MakePane(newTerminalArgs, nullptr, existingConnection)); + _CreateNewTabFromPane(_MakePane(newTerminalArgs, nullptr)); return S_OK; } CATCH_RETURN(); diff --git a/src/cascadia/TerminalApp/TerminalAppLib.vcxproj b/src/cascadia/TerminalApp/TerminalAppLib.vcxproj index 212c8c18283..552de66e160 100644 --- a/src/cascadia/TerminalApp/TerminalAppLib.vcxproj +++ b/src/cascadia/TerminalApp/TerminalAppLib.vcxproj @@ -158,6 +158,12 @@ TerminalWindow.idl + + TerminalPaneContent.idl + + + TerminalPaneContent.idl + SuggestionsControl.xaml @@ -262,6 +268,12 @@ TerminalWindow.idl + + TerminalPaneContent.idl + + + ScratchpadContent.idl + @@ -334,6 +346,8 @@ Code + + diff --git a/src/cascadia/TerminalApp/TerminalPage.cpp b/src/cascadia/TerminalApp/TerminalPage.cpp index 23ffbfcd900..ee8f3980cc3 100644 --- a/src/cascadia/TerminalApp/TerminalPage.cpp +++ b/src/cascadia/TerminalApp/TerminalPage.cpp @@ -1366,15 +1366,20 @@ namespace winrt::TerminalApp::implementation return connection; } - TerminalConnection::ITerminalConnection TerminalPage::_duplicateConnectionForRestart(std::shared_ptr pane) + TerminalConnection::ITerminalConnection TerminalPage::_duplicateConnectionForRestart(const TerminalApp::TerminalPaneContent& paneContent) { - const auto& control{ pane->GetTerminalControl() }; + if (paneContent == nullptr) + { + return nullptr; + } + + const auto& control{ paneContent.GetTermControl() }; if (control == nullptr) { return nullptr; } const auto& connection = control.Connection(); - auto profile{ pane->GetProfile() }; + auto profile{ paneContent.GetProfile() }; TerminalSettingsCreateResult controlSettings{ nullptr }; @@ -1806,11 +1811,8 @@ namespace winrt::TerminalApp::implementation // Add an event handler for when the terminal or tab wants to set a // progress indicator on the taskbar hostingTab.TaskbarProgressChanged({ get_weak(), &TerminalPage::_SetTaskbarProgressHandler }); - } - void TerminalPage::_RegisterPaneEvents(std::shared_ptr& pane) - { - pane->RestartTerminalRequested({ get_weak(), &TerminalPage::_restartPaneConnection }); + hostingTab.RestartTerminalRequested({ get_weak(), &TerminalPage::_restartPaneConnection }); } // Method Description: @@ -2459,13 +2461,6 @@ namespace winrt::TerminalApp::implementation _UnZoomIfNeeded(); auto [original, _] = activeTab->SplitPane(*realSplitType, splitSize, newPane); - // When we split the pane, the Pane itself will create a _new_ Pane - // instance for the original content. We need to make sure we also - // re-add our event handler to that newly created pane. - // - // _MakePane will already call this for the newly created pane. - _RegisterPaneEvents(original); - // After GH#6586, the control will no longer focus itself // automatically when it's finished being laid out. Manually focus // the control here instead. @@ -3196,10 +3191,9 @@ namespace winrt::TerminalApp::implementation // Don't need to worry about duplicating or anything - we'll // serialize the actual profile's GUID along with the content guid. const auto& profile = _settings.GetProfileForArgs(newTerminalArgs); - const auto control = _AttachControlToContent(newTerminalArgs.ContentId()); - - return std::make_shared(profile, control); + auto paneContent{ winrt::make(profile, control) }; + return std::make_shared(paneContent); } TerminalSettingsCreateResult controlSettings{ nullptr }; @@ -3255,13 +3249,15 @@ namespace winrt::TerminalApp::implementation const auto control = _CreateNewControlAndContent(controlSettings, connection); - auto resultPane = std::make_shared(profile, control); + auto paneContent{ winrt::make(profile, control) }; + auto resultPane = std::make_shared(paneContent); if (debugConnection) // this will only be set if global debugging is on and tap is active { auto newControl = _CreateNewControlAndContent(controlSettings, debugConnection); // Split (auto) with the debug tap. - auto debugPane = std::make_shared(profile, newControl); + auto debugContent{ winrt::make(profile, newControl) }; + auto debugPane = std::make_shared(debugContent); // Since we're doing this split directly on the pane (instead of going through TerminalTab, // we need to handle the panes 'active' states @@ -3275,16 +3271,20 @@ namespace winrt::TerminalApp::implementation original->SetActive(); } - _RegisterPaneEvents(resultPane); - return resultPane; } - void TerminalPage::_restartPaneConnection(const std::shared_ptr& pane) + void TerminalPage::_restartPaneConnection( + const TerminalApp::TerminalPaneContent& paneContent, + const winrt::Windows::Foundation::IInspectable&) { - if (const auto& connection{ _duplicateConnectionForRestart(pane) }) + // Note: callers are likely passing in `nullptr` as the args here, as + // the TermControl.RestartTerminalRequested event doesn't actually pass + // any args upwards itself. If we ever change this, make sure you check + // for nulls + if (const auto& connection{ _duplicateConnectionForRestart(paneContent) }) { - pane->GetTerminalControl().Connection(connection); + paneContent.GetTermControl().Connection(connection); connection.Start(); } } diff --git a/src/cascadia/TerminalApp/TerminalPage.h b/src/cascadia/TerminalApp/TerminalPage.h index 0b283630412..04da15ab93b 100644 --- a/src/cascadia/TerminalApp/TerminalPage.h +++ b/src/cascadia/TerminalApp/TerminalPage.h @@ -302,14 +302,14 @@ namespace winrt::TerminalApp::implementation winrt::Windows::UI::Xaml::Controls::MenuFlyoutItem _CreateNewTabFlyoutProfile(const Microsoft::Terminal::Settings::Model::Profile profile, int profileIndex); void _OpenNewTabDropdown(); - HRESULT _OpenNewTab(const Microsoft::Terminal::Settings::Model::NewTerminalArgs& newTerminalArgs, winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection existingConnection = nullptr); + HRESULT _OpenNewTab(const Microsoft::Terminal::Settings::Model::NewTerminalArgs& newTerminalArgs); void _CreateNewTabFromPane(std::shared_ptr pane, uint32_t insertPosition = -1); std::wstring _evaluatePathForCwd(std::wstring_view path); winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection _CreateConnectionFromSettings(Microsoft::Terminal::Settings::Model::Profile profile, Microsoft::Terminal::Settings::Model::TerminalSettings settings, const bool inheritCursor); - winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection _duplicateConnectionForRestart(std::shared_ptr pane); - void _restartPaneConnection(const std::shared_ptr& pane); + winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection _duplicateConnectionForRestart(const TerminalApp::TerminalPaneContent& paneContent); + void _restartPaneConnection(const TerminalApp::TerminalPaneContent&, const winrt::Windows::Foundation::IInspectable&); winrt::fire_and_forget _OpenNewWindow(const Microsoft::Terminal::Settings::Model::NewTerminalArgs newTerminalArgs); @@ -346,7 +346,6 @@ namespace winrt::TerminalApp::implementation void _InitializeTab(winrt::com_ptr newTabImpl, uint32_t insertPosition = -1); void _RegisterTerminalEvents(Microsoft::Terminal::Control::TermControl term); void _RegisterTabEvents(TerminalTab& hostingTab); - void _RegisterPaneEvents(std::shared_ptr& pane); void _DismissTabContextMenus(); void _FocusCurrentTab(const bool focusAlways); diff --git a/src/cascadia/TerminalApp/TerminalPaneContent.cpp b/src/cascadia/TerminalApp/TerminalPaneContent.cpp new file mode 100644 index 00000000000..597631d8fdc --- /dev/null +++ b/src/cascadia/TerminalApp/TerminalPaneContent.cpp @@ -0,0 +1,332 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#include "pch.h" +#include "TerminalPaneContent.h" +#include "TerminalPaneContent.g.cpp" + +#include "BellEventArgs.g.cpp" + +#include +using namespace winrt::Windows::Foundation; +using namespace winrt::Windows::UI::Xaml; +using namespace winrt::Microsoft::Terminal::Settings::Model; +using namespace winrt::Microsoft::Terminal::Control; +using namespace winrt::Microsoft::Terminal::TerminalConnection; + +namespace winrt::TerminalApp::implementation +{ + TerminalPaneContent::TerminalPaneContent(const winrt::Microsoft::Terminal::Settings::Model::Profile& profile, + const winrt::Microsoft::Terminal::Control::TermControl& control) : + _control{ control }, + _profile{ profile } + { + _setupControlEvents(); + } + + void TerminalPaneContent::_setupControlEvents() + { + _controlEvents._ConnectionStateChanged = _control.ConnectionStateChanged(winrt::auto_revoke, { this, &TerminalPaneContent::_controlConnectionStateChangedHandler }); + _controlEvents._WarningBell = _control.WarningBell(winrt::auto_revoke, { get_weak(), &TerminalPaneContent::_controlWarningBellHandler }); + _controlEvents._CloseTerminalRequested = _control.CloseTerminalRequested(winrt::auto_revoke, { get_weak(), &TerminalPaneContent::_closeTerminalRequestedHandler }); + _controlEvents._RestartTerminalRequested = _control.RestartTerminalRequested(winrt::auto_revoke, { get_weak(), &TerminalPaneContent::_restartTerminalRequestedHandler }); + + _controlEvents._TitleChanged = _control.TitleChanged(winrt::auto_revoke, { get_weak(), &TerminalPaneContent::_controlTitleChanged }); + _controlEvents._TabColorChanged = _control.TabColorChanged(winrt::auto_revoke, { get_weak(), &TerminalPaneContent::_controlTabColorChanged }); + _controlEvents._SetTaskbarProgress = _control.SetTaskbarProgress(winrt::auto_revoke, { get_weak(), &TerminalPaneContent::_controlSetTaskbarProgress }); + _controlEvents._ReadOnlyChanged = _control.ReadOnlyChanged(winrt::auto_revoke, { get_weak(), &TerminalPaneContent::_controlReadOnlyChanged }); + _controlEvents._FocusFollowMouseRequested = _control.FocusFollowMouseRequested(winrt::auto_revoke, { get_weak(), &TerminalPaneContent::_controlFocusFollowMouseRequested }); + } + void TerminalPaneContent::_removeControlEvents() + { + _controlEvents = {}; + } + + winrt::Windows::UI::Xaml::FrameworkElement TerminalPaneContent::GetRoot() + { + return _control; + } + winrt::Microsoft::Terminal::Control::TermControl TerminalPaneContent::GetTermControl() + { + return _control; + } + winrt::Windows::Foundation::Size TerminalPaneContent::MinimumSize() + { + return _control.MinimumSize(); + } + void TerminalPaneContent::Focus(winrt::Windows::UI::Xaml::FocusState reason) + { + _control.Focus(reason); + } + void TerminalPaneContent::Close() + { + _removeControlEvents(); + + // Clear out our media player callbacks, and stop any playing media. This + // will prevent the callback from being triggered after we've closed, and + // also make sure that our sound stops when we're closed. + if (_bellPlayer) + { + _bellPlayer.Pause(); + _bellPlayer.Source(nullptr); + _bellPlayer.Close(); + _bellPlayer = nullptr; + _bellPlayerCreated = false; + } + + CloseRequested.raise(*this, nullptr); + } + + NewTerminalArgs TerminalPaneContent::GetNewTerminalArgs(const bool asContent) const + { + NewTerminalArgs args{}; + const auto& controlSettings = _control.Settings(); + + args.Profile(controlSettings.ProfileName()); + // If we know the user's working directory use it instead of the profile. + if (const auto dir = _control.WorkingDirectory(); !dir.empty()) + { + args.StartingDirectory(dir); + } + else + { + args.StartingDirectory(controlSettings.StartingDirectory()); + } + args.TabTitle(controlSettings.StartingTitle()); + args.Commandline(controlSettings.Commandline()); + args.SuppressApplicationTitle(controlSettings.SuppressApplicationTitle()); + if (controlSettings.TabColor() || controlSettings.StartingTabColor()) + { + til::color c; + // StartingTabColor is prioritized over other colors + if (const auto color = controlSettings.StartingTabColor()) + { + c = til::color(color.Value()); + } + else + { + c = til::color(controlSettings.TabColor().Value()); + } + + args.TabColor(winrt::Windows::Foundation::IReference{ static_cast(c) }); + } + + // TODO:GH#9800 - we used to be able to persist the color scheme that a + // TermControl was initialized with, by name. With the change to having the + // control own its own copy of its settings, this isn't possible anymore. + // + // We may be able to get around this by storing the Name in the Core::Scheme + // object. That would work for schemes set by the Terminal, but not ones set + // by VT, but that seems good enough. + + // Only fill in the ContentId if absolutely needed. If you fill in a number + // here (even 0), we'll serialize that number, AND treat that action as an + // "attach existing" rather than a "create" + if (asContent) + { + args.ContentId(_control.ContentId()); + } + + return args; + } + + void TerminalPaneContent::_controlTitleChanged(const IInspectable&, const IInspectable&) + { + TitleChanged.raise(*this, nullptr); + } + void TerminalPaneContent::_controlTabColorChanged(const IInspectable&, const IInspectable&) + { + TabColorChanged.raise(*this, nullptr); + } + void TerminalPaneContent::_controlSetTaskbarProgress(const IInspectable&, const IInspectable&) + { + TaskbarProgressChanged.raise(*this, nullptr); + } + void TerminalPaneContent::_controlReadOnlyChanged(const IInspectable&, const IInspectable&) + { + ReadOnlyChanged.raise(*this, nullptr); + } + void TerminalPaneContent::_controlFocusFollowMouseRequested(const IInspectable&, const IInspectable&) + { + FocusRequested.raise(*this, nullptr); + } + + // Method Description: + // - Called when our attached control is closed. Triggers listeners to our close + // event, if we're a leaf pane. + // - If this was called, and we became a parent pane (due to work on another + // thread), this function will do nothing (allowing the control's new parent + // to handle the event instead). + // Arguments: + // - + // Return Value: + // - + winrt::fire_and_forget TerminalPaneContent::_controlConnectionStateChangedHandler(const winrt::Windows::Foundation::IInspectable& sender, + const winrt::Windows::Foundation::IInspectable& args) + { + ConnectionStateChanged.raise(sender, args); + auto newConnectionState = ConnectionState::Closed; + if (const auto coreState = sender.try_as()) + { + newConnectionState = coreState.ConnectionState(); + } + + const auto previousConnectionState = std::exchange(_connectionState, newConnectionState); + if (newConnectionState < ConnectionState::Closed) + { + // Pane doesn't care if the connection isn't entering a terminal state. + co_return; + } + + const auto weakThis = get_weak(); + co_await wil::resume_foreground(_control.Dispatcher()); + const auto strongThis = weakThis.get(); + if (!strongThis) + { + co_return; + } + + // It's possible that this event handler started being executed, scheduled + // on the UI thread, another child got created. So our control is + // actually no longer _our_ control, and instead could be a descendant. + // + // When the control's new Pane takes ownership of the control, the new + // parent will register its own event handler. That event handler will get + // fired after this handler returns, and will properly cleanup state. + + if (previousConnectionState < ConnectionState::Connected && newConnectionState >= ConnectionState::Failed) + { + // A failure to complete the connection (before it has _connected_) is not covered by "closeOnExit". + // This is to prevent a misconfiguration (closeOnExit: always, startingDirectory: garbage) resulting + // in Terminal flashing open and immediately closed. + co_return; + } + + if (_profile) + { + if (_isDefTermSession && _profile.CloseOnExit() == CloseOnExitMode::Automatic) + { + // For 'automatic', we only care about the connection state if we were launched by Terminal + // Since we were launched via defterm, ignore the connection state (i.e. we treat the + // close on exit mode as 'always', see GH #13325 for discussion) + Close(); + } + + const auto mode = _profile.CloseOnExit(); + if ((mode == CloseOnExitMode::Always) || + ((mode == CloseOnExitMode::Graceful || mode == CloseOnExitMode::Automatic) && newConnectionState == ConnectionState::Closed)) + { + Close(); + } + } + } + + // Method Description: + // - Plays a warning note when triggered by the BEL control character, + // using the sound configured for the "Critical Stop" system event.` + // This matches the behavior of the Windows Console host. + // - Will also flash the taskbar if the bellStyle setting for this profile + // has the 'visual' flag set + // Arguments: + // - + void TerminalPaneContent::_controlWarningBellHandler(const winrt::Windows::Foundation::IInspectable& /*sender*/, + const winrt::Windows::Foundation::IInspectable& /*eventArgs*/) + { + if (_profile) + { + // We don't want to do anything if nothing is set, so check for that first + if (static_cast(_profile.BellStyle()) != 0) + { + if (WI_IsFlagSet(_profile.BellStyle(), winrt::Microsoft::Terminal::Settings::Model::BellStyle::Audible)) + { + // Audible is set, play the sound + auto sounds{ _profile.BellSound() }; + if (sounds && sounds.Size() > 0) + { + winrt::hstring soundPath{ wil::ExpandEnvironmentStringsW(sounds.GetAt(rand() % sounds.Size()).c_str()) }; + winrt::Windows::Foundation::Uri uri{ soundPath }; + _playBellSound(uri); + } + else + { + const auto soundAlias = reinterpret_cast(SND_ALIAS_SYSTEMHAND); + PlaySound(soundAlias, NULL, SND_ALIAS_ID | SND_ASYNC | SND_SENTRY); + } + } + + if (WI_IsFlagSet(_profile.BellStyle(), winrt::Microsoft::Terminal::Settings::Model::BellStyle::Window)) + { + _control.BellLightOn(); + } + + // raise the event with the bool value corresponding to the taskbar flag + BellRequested.raise(*this, + *winrt::make_self(WI_IsFlagSet(_profile.BellStyle(), BellStyle::Taskbar))); + } + } + } + + winrt::fire_and_forget TerminalPaneContent::_playBellSound(winrt::Windows::Foundation::Uri uri) + { + auto weakThis{ get_weak() }; + co_await wil::resume_foreground(_control.Dispatcher()); + if (auto pane{ weakThis.get() }) + { + if (!_bellPlayerCreated) + { + // The MediaPlayer might not exist on Windows N SKU. + try + { + _bellPlayerCreated = true; + _bellPlayer = winrt::Windows::Media::Playback::MediaPlayer(); + // GH#12258: The media keys (like play/pause) should have no effect on our bell sound. + _bellPlayer.CommandManager().IsEnabled(false); + } + CATCH_LOG(); + } + if (_bellPlayer) + { + const auto source{ winrt::Windows::Media::Core::MediaSource::CreateFromUri(uri) }; + const auto item{ winrt::Windows::Media::Playback::MediaPlaybackItem(source) }; + _bellPlayer.Source(item); + _bellPlayer.Play(); + } + } + } + void TerminalPaneContent::_closeTerminalRequestedHandler(const winrt::Windows::Foundation::IInspectable& /*sender*/, + const winrt::Windows::Foundation::IInspectable& /*args*/) + { + Close(); + } + + void TerminalPaneContent::_restartTerminalRequestedHandler(const winrt::Windows::Foundation::IInspectable& /*sender*/, + const winrt::Windows::Foundation::IInspectable& /*args*/) + { + RestartTerminalRequested.raise(*this, nullptr); + } + + void TerminalPaneContent::UpdateSettings(const TerminalSettingsCreateResult& settings, + const Profile& profile) + { + _profile = profile; + _control.UpdateControlSettings(settings.DefaultSettings(), settings.UnfocusedSettings()); + } + + // Method Description: + // - Should be called when this pane is created via a default terminal handoff + // - Finalizes our configuration given the information that we have been + // created via default handoff + void TerminalPaneContent::MarkAsDefterm() + { + _isDefTermSession = true; + } + + float TerminalPaneContent::SnapDownToGrid(const TerminalApp::PaneSnapDirection direction, const float sizeToSnap) + { + return _control.SnapDimensionToGrid(direction == PaneSnapDirection::Width, sizeToSnap); + } + Windows::Foundation::Size TerminalPaneContent::GridUnitSize() + { + return _control.CharacterDimensions(); + } +} diff --git a/src/cascadia/TerminalApp/TerminalPaneContent.h b/src/cascadia/TerminalApp/TerminalPaneContent.h new file mode 100644 index 00000000000..2c2deee0124 --- /dev/null +++ b/src/cascadia/TerminalApp/TerminalPaneContent.h @@ -0,0 +1,102 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#pragma once +#include "TerminalPaneContent.g.h" +#include "BellEventArgs.g.h" + +namespace winrt::TerminalApp::implementation +{ + struct BellEventArgs : public BellEventArgsT + { + public: + BellEventArgs(bool flashTaskbar) : + FlashTaskbar(flashTaskbar) {} + + til::property FlashTaskbar; + }; + + struct TerminalPaneContent : TerminalPaneContentT + { + TerminalPaneContent(const winrt::Microsoft::Terminal::Settings::Model::Profile& profile, + const winrt::Microsoft::Terminal::Control::TermControl& control); + + winrt::Windows::UI::Xaml::FrameworkElement GetRoot(); + winrt::Microsoft::Terminal::Control::TermControl GetTermControl(); + winrt::Windows::Foundation::Size MinimumSize(); + void Focus(winrt::Windows::UI::Xaml::FocusState reason = winrt::Windows::UI::Xaml::FocusState::Programmatic); + void Close(); + + winrt::Microsoft::Terminal::Settings::Model::NewTerminalArgs GetNewTerminalArgs(const bool asContent) const; + + void UpdateSettings(const winrt::Microsoft::Terminal::Settings::Model::TerminalSettingsCreateResult& settings, + const winrt::Microsoft::Terminal::Settings::Model::Profile& profile); + + void MarkAsDefterm(); + + winrt::Microsoft::Terminal::Settings::Model::Profile GetProfile() const + { + return _profile; + } + + winrt::hstring Title() { return _control.Title(); } + uint64_t TaskbarState() { return _control.TaskbarState(); } + uint64_t TaskbarProgress() { return _control.TaskbarProgress(); } + bool ReadOnly() { return _control.ReadOnly(); } + + float SnapDownToGrid(const TerminalApp::PaneSnapDirection direction, const float sizeToSnap); + Windows::Foundation::Size GridUnitSize(); + + til::typed_event RestartTerminalRequested; + til::typed_event<> ConnectionStateChanged; + til::typed_event CloseRequested; + til::typed_event BellRequested; + til::typed_event TitleChanged; + til::typed_event TabColorChanged; + til::typed_event TaskbarProgressChanged; + til::typed_event ReadOnlyChanged; + til::typed_event FocusRequested; + + private: + winrt::Microsoft::Terminal::Control::TermControl _control{ nullptr }; + winrt::Microsoft::Terminal::TerminalConnection::ConnectionState _connectionState{ winrt::Microsoft::Terminal::TerminalConnection::ConnectionState::NotConnected }; + winrt::Microsoft::Terminal::Settings::Model::Profile _profile{ nullptr }; + bool _isDefTermSession{ false }; + + winrt::Windows::Media::Playback::MediaPlayer _bellPlayer{ nullptr }; + bool _bellPlayerCreated{ false }; + + struct ControlEventTokens + { + winrt::Microsoft::Terminal::Control::TermControl::ConnectionStateChanged_revoker _ConnectionStateChanged; + winrt::Microsoft::Terminal::Control::TermControl::WarningBell_revoker _WarningBell; + winrt::Microsoft::Terminal::Control::TermControl::CloseTerminalRequested_revoker _CloseTerminalRequested; + winrt::Microsoft::Terminal::Control::TermControl::RestartTerminalRequested_revoker _RestartTerminalRequested; + + winrt::Microsoft::Terminal::Control::TermControl::TitleChanged_revoker _TitleChanged; + winrt::Microsoft::Terminal::Control::TermControl::TabColorChanged_revoker _TabColorChanged; + winrt::Microsoft::Terminal::Control::TermControl::SetTaskbarProgress_revoker _SetTaskbarProgress; + winrt::Microsoft::Terminal::Control::TermControl::ReadOnlyChanged_revoker _ReadOnlyChanged; + winrt::Microsoft::Terminal::Control::TermControl::FocusFollowMouseRequested_revoker _FocusFollowMouseRequested; + + } _controlEvents; + void _setupControlEvents(); + void _removeControlEvents(); + + winrt::fire_and_forget _playBellSound(winrt::Windows::Foundation::Uri uri); + + winrt::fire_and_forget _controlConnectionStateChangedHandler(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Windows::Foundation::IInspectable& /*args*/); + void _controlWarningBellHandler(const winrt::Windows::Foundation::IInspectable& sender, + const winrt::Windows::Foundation::IInspectable& e); + void _controlReadOnlyChangedHandler(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Windows::Foundation::IInspectable& e); + + void _controlTitleChanged(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Windows::Foundation::IInspectable& args); + void _controlTabColorChanged(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Windows::Foundation::IInspectable& args); + void _controlSetTaskbarProgress(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Windows::Foundation::IInspectable& args); + void _controlReadOnlyChanged(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Windows::Foundation::IInspectable& args); + void _controlFocusFollowMouseRequested(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Windows::Foundation::IInspectable& args); + + void _closeTerminalRequestedHandler(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Windows::Foundation::IInspectable& /*args*/); + void _restartTerminalRequestedHandler(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Windows::Foundation::IInspectable& /*args*/); + }; +} diff --git a/src/cascadia/TerminalApp/TerminalPaneContent.idl b/src/cascadia/TerminalApp/TerminalPaneContent.idl new file mode 100644 index 00000000000..7e04c8b836c --- /dev/null +++ b/src/cascadia/TerminalApp/TerminalPaneContent.idl @@ -0,0 +1,21 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +import "IPaneContent.idl"; + +namespace TerminalApp +{ + [default_interface] runtimeclass TerminalPaneContent : IPaneContent, ISnappable + { + Microsoft.Terminal.Control.TermControl GetTermControl(); + + void UpdateSettings(const Microsoft.Terminal.Settings.Model.TerminalSettingsCreateResult settings, + const Microsoft.Terminal.Settings.Model.Profile profile); + + void MarkAsDefterm(); + + Microsoft.Terminal.Settings.Model.Profile GetProfile(); + + event Windows.Foundation.TypedEventHandler RestartTerminalRequested; + } +} diff --git a/src/cascadia/TerminalApp/TerminalTab.cpp b/src/cascadia/TerminalApp/TerminalTab.cpp index b3d37d88a17..c5cb7f5b5f2 100644 --- a/src/cascadia/TerminalApp/TerminalTab.cpp +++ b/src/cascadia/TerminalApp/TerminalTab.cpp @@ -186,6 +186,11 @@ namespace winrt::TerminalApp::implementation return nullptr; } + IPaneContent TerminalTab::GetActiveContent() const + { + return _activePane ? _activePane->GetContent() : nullptr; + } + // Method Description: // - Called after construction of a Tab object to bind event handlers to its // associated Pane and TermControl objects @@ -200,9 +205,9 @@ namespace winrt::TerminalApp::implementation _rootPane->WalkTree([&](std::shared_ptr pane) { // Attach event handlers to each new pane _AttachEventHandlersToPane(pane); - if (auto control = pane->GetTerminalControl()) + if (auto content = pane->GetContent()) { - _AttachEventHandlersToControl(pane->Id().value(), control); + _AttachEventHandlersToContent(pane->Id().value(), content); } }); } @@ -377,8 +382,8 @@ namespace winrt::TerminalApp::implementation { return RS_(L"MultiplePanes"); } - const auto lastFocusedControl = GetActiveTerminalControl(); - return lastFocusedControl ? lastFocusedControl.Title() : L""; + const auto activeContent = GetActiveContent(); + return activeContent ? activeContent.Title() : L""; } // Method Description: @@ -513,7 +518,10 @@ namespace winrt::TerminalApp::implementation if (p->_IsLeaf()) { p->Id(_nextPaneId); - _AttachEventHandlersToControl(p->Id().value(), p->_control); + if (const auto& content{ p->GetContent() }) + { + _AttachEventHandlersToContent(p->Id().value(), content); + } _nextPaneId++; } return false; @@ -626,7 +634,11 @@ namespace winrt::TerminalApp::implementation if (p->_IsLeaf()) { p->Id(_nextPaneId); - _AttachEventHandlersToControl(p->Id().value(), p->_control); + + if (const auto& content{ p->GetContent() }) + { + _AttachEventHandlersToContent(p->Id().value(), content); + } _nextPaneId++; } }); @@ -883,29 +895,17 @@ namespace winrt::TerminalApp::implementation // the control itself doesn't have a particular ID and its pointer is // unstable since it is moved when panes split. // Arguments: - // - paneId: The ID of the pane that contains the given control. - // - control: the control to remove events from. + // - paneId: The ID of the pane that contains the given content. // Return Value: // - - void TerminalTab::_DetachEventHandlersFromControl(const uint32_t paneId, const TermControl& control) + void TerminalTab::_DetachEventHandlersFromContent(const uint32_t paneId) { - auto it = _controlEvents.find(paneId); - if (it != _controlEvents.end()) + auto it = _contentEvents.find(paneId); + if (it != _contentEvents.end()) { - auto& events = it->second; - - control.TitleChanged(events.titleToken); - control.TabColorChanged(events.colorToken); - control.SetTaskbarProgress(events.taskbarToken); - control.ConnectionStateChanged(events.stateToken); - control.ReadOnlyChanged(events.readOnlyToken); - control.FocusFollowMouseRequested(events.focusToken); - - events.KeySent.revoke(); - events.CharSent.revoke(); - events.StringSent.revoke(); - - _controlEvents.erase(paneId); + // revoke the event handlers by resetting the event struct + // and remove it from the map + _contentEvents.erase(paneId); } } @@ -920,87 +920,159 @@ namespace winrt::TerminalApp::implementation // - control: the TermControl to add events to. // Return Value: // - - void TerminalTab::_AttachEventHandlersToControl(const uint32_t paneId, const TermControl& control) + void TerminalTab::_AttachEventHandlersToContent(const uint32_t paneId, const TerminalApp::IPaneContent& content) { auto weakThis{ get_weak() }; auto dispatcher = TabViewItem().Dispatcher(); - ControlEventTokens events{}; - - events.titleToken = control.TitleChanged([dispatcher, weakThis](auto&&, auto&&) -> winrt::fire_and_forget { - // The lambda lives in the `std::function`-style container owned by `control`. That is, when the - // `control` gets destroyed the lambda struct also gets destroyed. In other words, we need to - // copy `weakThis` onto the stack, because that's the only thing that gets captured in coroutines. - // See: https://devblogs.microsoft.com/oldnewthing/20211103-00/?p=105870 - const auto weakThisCopy = weakThis; - co_await wil::resume_foreground(dispatcher); - if (auto tab{ weakThisCopy.get() }) - { - // The title of the control changed, but not necessarily the title of the tab. - // Set the tab's text to the active panes' text. - tab->UpdateTitle(); - } - }); + ContentEventTokens events{}; + + events.CloseRequested = content.CloseRequested( + winrt::auto_revoke, + [dispatcher, weakThis](auto sender, auto&&) -> winrt::fire_and_forget { + // Don't forget! this ^^^^^^^^ sender can't be a reference, this is a async callback. + + // The lambda lives in the `std::function`-style container owned by `control`. That is, when the + // `control` gets destroyed the lambda struct also gets destroyed. In other words, we need to + // copy `weakThis` onto the stack, because that's the only thing that gets captured in coroutines. + // See: https://devblogs.microsoft.com/oldnewthing/20211103-00/?p=105870 + const auto weakThisCopy = weakThis; + co_await wil::resume_foreground(dispatcher); + // Check if Tab's lifetime has expired + if (auto tab{ weakThisCopy.get() }) + { + if (const auto content{ sender.try_as() }) + { + tab->_rootPane->WalkTree([content](std::shared_ptr pane) { + if (pane->GetContent() == content) + { + pane->Close(); + } + }); + } + } + }); - events.colorToken = control.TabColorChanged([dispatcher, weakThis](auto&&, auto&&) -> winrt::fire_and_forget { - const auto weakThisCopy = weakThis; - co_await wil::resume_foreground(dispatcher); - if (auto tab{ weakThisCopy.get() }) - { - // The control's tabColor changed, but it is not necessarily the - // active control in this tab. We'll just recalculate the - // current color anyways. - tab->_RecalculateAndApplyTabColor(); - } - }); + events.TitleChanged = content.TitleChanged( + winrt::auto_revoke, + [dispatcher, weakThis](auto&&, auto&&) -> winrt::fire_and_forget { + // The lambda lives in the `std::function`-style container owned by `control`. That is, when the + // `control` gets destroyed the lambda struct also gets destroyed. In other words, we need to + // copy `weakThis` onto the stack, because that's the only thing that gets captured in coroutines. + // See: https://devblogs.microsoft.com/oldnewthing/20211103-00/?p=105870 + const auto weakThisCopy = weakThis; + co_await wil::resume_foreground(dispatcher); + // Check if Tab's lifetime has expired + if (auto tab{ weakThisCopy.get() }) + { + // The title of the control changed, but not necessarily the title of the tab. + // Set the tab's text to the active panes' text. + tab->UpdateTitle(); + } + }); - events.taskbarToken = control.SetTaskbarProgress([dispatcher, weakThis](auto&&, auto&&) -> winrt::fire_and_forget { - const auto weakThisCopy = weakThis; - co_await wil::resume_foreground(dispatcher); - if (auto tab{ weakThisCopy.get() }) - { - tab->_UpdateProgressState(); - } - }); + events.TabColorChanged = content.TabColorChanged( + winrt::auto_revoke, + [dispatcher, weakThis](auto&&, auto&&) -> winrt::fire_and_forget { + const auto weakThisCopy = weakThis; + co_await wil::resume_foreground(dispatcher); + if (auto tab{ weakThisCopy.get() }) + { + // The control's tabColor changed, but it is not necessarily the + // active control in this tab. We'll just recalculate the + // current color anyways. + tab->_RecalculateAndApplyTabColor(); + } + }); - events.stateToken = control.ConnectionStateChanged([dispatcher, weakThis](auto&&, auto&&) -> winrt::fire_and_forget { - const auto weakThisCopy = weakThis; - co_await wil::resume_foreground(dispatcher); - if (auto tab{ weakThisCopy.get() }) - { - tab->_UpdateConnectionClosedState(); - } - }); + events.TaskbarProgressChanged = content.TaskbarProgressChanged( + winrt::auto_revoke, + [dispatcher, weakThis](auto&&, auto&&) -> winrt::fire_and_forget { + const auto weakThisCopy = weakThis; + co_await wil::resume_foreground(dispatcher); + // Check if Tab's lifetime has expired + if (auto tab{ weakThisCopy.get() }) + { + tab->_UpdateProgressState(); + } + }); - events.readOnlyToken = control.ReadOnlyChanged([dispatcher, weakThis](auto&&, auto&&) -> winrt::fire_and_forget { - const auto weakThisCopy = weakThis; - co_await wil::resume_foreground(dispatcher); - if (auto tab{ weakThisCopy.get() }) - { - tab->_RecalculateAndApplyReadOnly(); - } - }); + events.ConnectionStateChanged = content.ConnectionStateChanged( + winrt::auto_revoke, + [dispatcher, weakThis](auto&&, auto&&) -> winrt::fire_and_forget { + const auto weakThisCopy = weakThis; + co_await wil::resume_foreground(dispatcher); + if (auto tab{ weakThisCopy.get() }) + { + tab->_UpdateConnectionClosedState(); + } + }); - events.focusToken = control.FocusFollowMouseRequested([dispatcher, weakThis](auto sender, auto) -> winrt::fire_and_forget { - const auto weakThisCopy = weakThis; - co_await wil::resume_foreground(dispatcher); - if (const auto tab{ weakThisCopy.get() }) - { - if (tab->_focused()) + events.ReadOnlyChanged = content.ReadOnlyChanged( + winrt::auto_revoke, + [dispatcher, weakThis](auto&&, auto&&) -> winrt::fire_and_forget { + const auto weakThisCopy = weakThis; + co_await wil::resume_foreground(dispatcher); + if (auto tab{ weakThis.get() }) { - if (const auto termControl{ sender.try_as() }) + tab->_RecalculateAndApplyReadOnly(); + } + }); + + events.FocusRequested = content.FocusRequested( + winrt::auto_revoke, + [dispatcher, weakThis](TerminalApp::IPaneContent sender, auto) -> winrt::fire_and_forget { + const auto weakThisCopy = weakThis; + co_await wil::resume_foreground(dispatcher); + if (const auto tab{ weakThisCopy.get() }) + { + if (tab->_focused()) { - termControl.Focus(FocusState::Pointer); + sender.Focus(FocusState::Pointer); } } - } - }); + }); + + events.BellRequested = content.BellRequested( + winrt::auto_revoke, + [dispatcher, weakThis](TerminalApp::IPaneContent sender, auto bellArgs) -> winrt::fire_and_forget { + const auto weakThisCopy = weakThis; + co_await wil::resume_foreground(dispatcher); + if (const auto tab{ weakThisCopy.get() }) + { + if (bellArgs.FlashTaskbar()) + { + // If visual is set, we need to bubble this event all the way to app host to flash the taskbar + // In this part of the chain we bubble it from the hosting tab to the page + tab->TabRaiseVisualBell.raise(); + } + + // Show the bell indicator in the tab header + tab->ShowBellIndicator(true); + + // If this tab is focused, activate the bell indicator timer, which will + // remove the bell indicator once it fires + // (otherwise, the indicator is removed when the tab gets focus) + if (tab->_focusState != WUX::FocusState::Unfocused) + { + tab->ActivateBellIndicatorTimer(); + } + } + }); + + if (const auto& terminal{ content.try_as() }) + { + events.RestartTerminalRequested = terminal.RestartTerminalRequested(winrt::auto_revoke, { get_weak(), &TerminalTab::_bubbleRestartTerminalRequested }); + } if (_tabStatus.IsInputBroadcastActive()) { - _addBroadcastHandlers(control, events); + if (const auto& termContent{ content.try_as() }) + { + _addBroadcastHandlers(termContent.GetTermControl(), events); + } } - _controlEvents[paneId] = std::move(events); + _contentEvents[paneId] = std::move(events); } // Method Description: @@ -1250,36 +1322,12 @@ namespace winrt::TerminalApp::implementation } }); - // Add a PaneRaiseBell event handler to the Pane - auto bellToken = pane->PaneRaiseBell([weakThis](auto&& /*s*/, auto&& visual) { - if (auto tab{ weakThis.get() }) - { - if (visual) - { - // If visual is set, we need to bubble this event all the way to app host to flash the taskbar - // In this part of the chain we bubble it from the hosting tab to the page - tab->TabRaiseVisualBell.raise(); - } - - // Show the bell indicator in the tab header - tab->ShowBellIndicator(true); - - // If this tab is focused, activate the bell indicator timer, which will - // remove the bell indicator once it fires - // (otherwise, the indicator is removed when the tab gets focus) - if (tab->_focusState != WUX::FocusState::Unfocused) - { - tab->ActivateBellIndicatorTimer(); - } - } - }); - // box the event token so that we can give a reference to it in the // event handler. auto detachedToken = std::make_shared(); // Add a Detached event handler to the Pane to clean up tab state // and other event handlers when a pane is removed from this tab. - *detachedToken = pane->Detached([weakThis, weakPane, gotFocusToken, lostFocusToken, closedToken, bellToken, detachedToken](std::shared_ptr /*sender*/) { + *detachedToken = pane->Detached([weakThis, weakPane, gotFocusToken, lostFocusToken, closedToken, detachedToken](std::shared_ptr /*sender*/) { // Make sure we do this at most once if (auto pane{ weakPane.lock() }) { @@ -1287,14 +1335,10 @@ namespace winrt::TerminalApp::implementation pane->GotFocus(gotFocusToken); pane->LostFocus(lostFocusToken); pane->Closed(closedToken); - pane->PaneRaiseBell(bellToken); if (auto tab{ weakThis.get() }) { - if (auto control = pane->GetTerminalControl()) - { - tab->_DetachEventHandlersFromControl(pane->Id().value(), control); - } + tab->_DetachEventHandlersFromContent(pane->Id().value()); for (auto i = tab->_mruPanes.begin(); i != tab->_mruPanes.end(); ++i) { @@ -1495,7 +1539,12 @@ namespace winrt::TerminalApp::implementation // GH#10112 - if we're opening the tab renamer, don't // immediately toss focus to the control. We don't want to steal // focus from the tab renamer. - if (!tab->_headerControl.InRename() && !tab->GetActiveTerminalControl().SearchBoxEditInFocus()) + const auto& terminalControl{ tab->GetActiveTerminalControl() }; // maybe null + // If we're + // * NOT in a rename + // * AND (the content isn't a TermControl, OR the term control doesn't have focus in the search box) + if (!tab->_headerControl.InRename() && + (terminalControl == nullptr || !terminalControl.SearchBoxEditInFocus())) { tab->RequestFocusActiveControl.raise(); } @@ -1695,6 +1744,18 @@ namespace winrt::TerminalApp::implementation return _zoomedPane != nullptr; } + TermControl _termControlFromPane(const auto& pane) + { + if (const auto content{ pane->GetContent() }) + { + if (const auto termContent{ content.try_as() }) + { + return termContent.GetTermControl(); + } + } + return nullptr; + } + // Method Description: // - Toggle read-only mode on the active pane // - If a parent pane is selected, this will ensure that all children have @@ -1706,14 +1767,14 @@ namespace winrt::TerminalApp::implementation auto hasReadOnly = false; auto allReadOnly = true; _activePane->WalkTree([&](const auto& p) { - if (const auto& control{ p->GetTerminalControl() }) + if (const auto& control{ _termControlFromPane(p) }) { hasReadOnly |= control.ReadOnly(); allReadOnly &= control.ReadOnly(); } }); _activePane->WalkTree([&](const auto& p) { - if (const auto& control{ p->GetTerminalControl() }) + if (const auto& control{ _termControlFromPane(p) }) { // If all controls have the same read only state then just toggle if (allReadOnly || !hasReadOnly) @@ -1738,14 +1799,14 @@ namespace winrt::TerminalApp::implementation auto hasReadOnly = false; auto allReadOnly = true; _activePane->WalkTree([&](const auto& p) { - if (const auto& control{ p->GetTerminalControl() }) + if (const auto& control{ _termControlFromPane(p) }) { hasReadOnly |= control.ReadOnly(); allReadOnly &= control.ReadOnly(); } }); _activePane->WalkTree([&](const auto& p) { - if (const auto& control{ p->GetTerminalControl() }) + if (const auto& control{ _termControlFromPane(p) }) { // If all controls have the same read only state then just disable if (allReadOnly || !hasReadOnly) @@ -1776,6 +1837,10 @@ namespace winrt::TerminalApp::implementation ReadOnly(_rootPane->ContainsReadOnly()); _updateIsClosable(); + + // Update all the visuals on all our panes, so they can update their + // border colors accordingly. + _rootPane->WalkTree([](const auto& p) { p->UpdateVisuals(); }); } std::shared_ptr TerminalTab::GetActivePane() const @@ -1826,10 +1891,10 @@ namespace winrt::TerminalApp::implementation { return; } - if (const auto& control{ p->GetTerminalControl() }) + if (const auto& control{ _termControlFromPane(p) }) { - auto it = _controlEvents.find(*paneId); - if (it != _controlEvents.end()) + auto it = _contentEvents.find(*paneId); + if (it != _contentEvents.end()) { auto& events = it->second; @@ -1847,7 +1912,7 @@ namespace winrt::TerminalApp::implementation }); } - void TerminalTab::_addBroadcastHandlers(const TermControl& termControl, ControlEventTokens& events) + void TerminalTab::_addBroadcastHandlers(const TermControl& termControl, ContentEventTokens& events) { auto weakThis{ get_weak() }; // ADD EVENT HANDLERS HERE @@ -1937,4 +2002,9 @@ namespace winrt::TerminalApp::implementation ActionAndArgs actionAndArgs{ ShortcutAction::Find, nullptr }; _dispatch.DoAction(*this, actionAndArgs); } + void TerminalTab::_bubbleRestartTerminalRequested(TerminalApp::TerminalPaneContent sender, + const winrt::Windows::Foundation::IInspectable& args) + { + RestartTerminalRequested.raise(sender, args); + } } diff --git a/src/cascadia/TerminalApp/TerminalTab.h b/src/cascadia/TerminalApp/TerminalTab.h index ca5901af648..df9e4cae825 100644 --- a/src/cascadia/TerminalApp/TerminalTab.h +++ b/src/cascadia/TerminalApp/TerminalTab.h @@ -25,6 +25,7 @@ namespace winrt::TerminalApp::implementation winrt::Microsoft::Terminal::Control::TermControl GetActiveTerminalControl() const; winrt::Microsoft::Terminal::Settings::Model::Profile GetFocusedProfile() const noexcept; + winrt::TerminalApp::IPaneContent GetActiveContent() const; void Focus(winrt::Windows::UI::Xaml::FocusState focusState) override; @@ -96,6 +97,8 @@ namespace winrt::TerminalApp::implementation return _tabStatus; } + til::typed_event RestartTerminalRequested; + til::event> ActivePaneChanged; til::event> TabRaiseVisualBell; til::typed_event TaskbarProgressChanged; @@ -122,20 +125,25 @@ namespace winrt::TerminalApp::implementation winrt::event_token _colorClearedToken; winrt::event_token _pickerClosedToken; - struct ControlEventTokens + struct ContentEventTokens { - winrt::event_token titleToken; - winrt::event_token colorToken; - winrt::event_token taskbarToken; - winrt::event_token stateToken; - winrt::event_token readOnlyToken; - winrt::event_token focusToken; - + winrt::TerminalApp::IPaneContent::BellRequested_revoker BellRequested; + winrt::TerminalApp::IPaneContent::TitleChanged_revoker TitleChanged; + winrt::TerminalApp::IPaneContent::TabColorChanged_revoker TabColorChanged; + winrt::TerminalApp::IPaneContent::TaskbarProgressChanged_revoker TaskbarProgressChanged; + winrt::TerminalApp::IPaneContent::ConnectionStateChanged_revoker ConnectionStateChanged; + winrt::TerminalApp::IPaneContent::ReadOnlyChanged_revoker ReadOnlyChanged; + winrt::TerminalApp::IPaneContent::FocusRequested_revoker FocusRequested; + winrt::TerminalApp::IPaneContent::CloseRequested_revoker CloseRequested; + + // These events literally only apply if the content is a TermControl. winrt::Microsoft::Terminal::Control::TermControl::KeySent_revoker KeySent; winrt::Microsoft::Terminal::Control::TermControl::CharSent_revoker CharSent; winrt::Microsoft::Terminal::Control::TermControl::StringSent_revoker StringSent; + + winrt::TerminalApp::TerminalPaneContent::RestartTerminalRequested_revoker RestartTerminalRequested; }; - std::unordered_map _controlEvents; + std::unordered_map _contentEvents; winrt::event_token _rootClosedToken{}; @@ -162,8 +170,8 @@ namespace winrt::TerminalApp::implementation void _CreateContextMenu() override; virtual winrt::hstring _CreateToolTipTitle() override; - void _DetachEventHandlersFromControl(const uint32_t paneId, const winrt::Microsoft::Terminal::Control::TermControl& control); - void _AttachEventHandlersToControl(const uint32_t paneId, const winrt::Microsoft::Terminal::Control::TermControl& control); + void _DetachEventHandlersFromContent(const uint32_t paneId); + void _AttachEventHandlersToContent(const uint32_t paneId, const winrt::TerminalApp::IPaneContent& content); void _AttachEventHandlersToPane(std::shared_ptr pane); void _UpdateActivePane(std::shared_ptr pane); @@ -181,7 +189,7 @@ namespace winrt::TerminalApp::implementation virtual winrt::Windows::UI::Xaml::Media::Brush _BackgroundBrush() override; - void _addBroadcastHandlers(const winrt::Microsoft::Terminal::Control::TermControl& control, ControlEventTokens& events); + void _addBroadcastHandlers(const winrt::Microsoft::Terminal::Control::TermControl& control, ContentEventTokens& events); void _chooseColorClicked(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Windows::UI::Xaml::RoutedEventArgs& e); void _renameTabClicked(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Windows::UI::Xaml::RoutedEventArgs& e); @@ -192,6 +200,8 @@ namespace winrt::TerminalApp::implementation void _moveTabToNewWindowClicked(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Windows::UI::Xaml::RoutedEventArgs& e); void _findClicked(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Windows::UI::Xaml::RoutedEventArgs& e); + void _bubbleRestartTerminalRequested(TerminalApp::TerminalPaneContent sender, const winrt::Windows::Foundation::IInspectable& args); + friend class ::TerminalAppLocalTests::TabTests; }; } diff --git a/src/cascadia/TerminalApp/pch.h b/src/cascadia/TerminalApp/pch.h index 0f4129dfa71..03a680ef7a5 100644 --- a/src/cascadia/TerminalApp/pch.h +++ b/src/cascadia/TerminalApp/pch.h @@ -85,6 +85,7 @@ TRACELOGGING_DECLARE_PROVIDER(g_hTerminalAppProvider); // Manually include til after we include Windows.Foundation to give it winrt superpowers #include "til.h" +#include #include diff --git a/src/cascadia/TerminalControl/ControlCore.cpp b/src/cascadia/TerminalControl/ControlCore.cpp index 4027e914d8f..98c70398559 100644 --- a/src/cascadia/TerminalControl/ControlCore.cpp +++ b/src/cascadia/TerminalControl/ControlCore.cpp @@ -9,14 +9,13 @@ #include #include -#include #include -#include #include #include "EventArgs.h" -#include "../../buffer/out/search.h" #include "../../renderer/atlas/AtlasEngine.h" +#include "../../renderer/base/renderer.hpp" +#include "../../renderer/uia/UiaRenderer.hpp" #include "ControlCore.g.cpp" #include "SelectionColor.g.cpp" @@ -43,26 +42,26 @@ constexpr const auto SearchAfterChangeDelay = std::chrono::milliseconds(200); namespace winrt::Microsoft::Terminal::Control::implementation { - static winrt::Microsoft::Terminal::Core::OptionalColor OptionalFromColor(const til::color& c) + static winrt::Microsoft::Terminal::Core::OptionalColor OptionalFromColor(const til::color& c) noexcept { Core::OptionalColor result; result.Color = c; result.HasValue = true; return result; } - static winrt::Microsoft::Terminal::Core::OptionalColor OptionalFromColor(const std::optional& c) + + static ::Microsoft::Console::Render::Atlas::GraphicsAPI parseGraphicsAPI(GraphicsAPI api) noexcept { - Core::OptionalColor result; - if (c.has_value()) - { - result.Color = *c; - result.HasValue = true; - } - else + using GA = ::Microsoft::Console::Render::Atlas::GraphicsAPI; + switch (api) { - result.HasValue = false; + case GraphicsAPI::Direct2D: + return GA::Direct2D; + case GraphicsAPI::Direct3D11: + return GA::Direct3D11; + default: + return GA::Automatic; } - return result; } TextColor SelectionColor::AsTextColor() const noexcept @@ -337,6 +336,11 @@ namespace winrt::Microsoft::Terminal::Control::implementation _renderEngine = std::make_unique<::Microsoft::Console::Render::AtlasEngine>(); _renderer->AddRenderEngine(_renderEngine.get()); + // Hook up the warnings callback as early as possible so that we catch everything. + _renderEngine->SetWarningCallback([this](HRESULT hr, wil::zwstring_view parameter) { + _rendererWarning(hr, parameter); + }); + // Initialize our font with the renderer // We don't have to care about DPI. We'll get a change message immediately if it's not 96 // and react accordingly. @@ -372,12 +376,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation _terminal->CreateFromSettings(*_settings, *_renderer); - // IMPORTANT! Set this callback up sooner than later. If we do it - // after Enable, then it'll be possible to paint the frame once - // _before_ the warning handler is set up, and then warnings from - // the first paint will be ignored! - _renderEngine->SetWarningCallback(std::bind(&ControlCore::_rendererWarning, this, std::placeholders::_1)); - // Tell the render engine to notify us when the swap chain changes. // We do this after we initially set the swapchain so as to avoid // unnecessary callbacks (and locking problems) @@ -388,7 +386,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation _renderEngine->SetRetroTerminalEffect(_settings->RetroTerminalEffect()); _renderEngine->SetPixelShaderPath(_settings->PixelShaderPath()); _renderEngine->SetPixelShaderImagePath(_settings->PixelShaderImagePath()); - _renderEngine->SetForceFullRepaintRendering(_settings->ForceFullRepaintRendering()); + _renderEngine->SetGraphicsAPI(parseGraphicsAPI(_settings->GraphicsAPI())); + _renderEngine->SetDisablePartialInvalidation(_settings->DisablePartialInvalidation()); _renderEngine->SetSoftwareRendering(_settings->SoftwareRendering()); _updateAntiAliasingMode(); @@ -397,8 +396,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation // GH#11315: Always do this, even if they don't have acrylic on. _renderEngine->EnableTransparentBackground(_isBackgroundTransparent()); - THROW_IF_FAILED(_renderEngine->Enable()); - _initializedTerminal.store(true, std::memory_order_relaxed); } // scope for TerminalLock @@ -883,7 +880,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation return; } - _renderEngine->SetForceFullRepaintRendering(_settings->ForceFullRepaintRendering()); + _renderEngine->SetGraphicsAPI(parseGraphicsAPI(_settings->GraphicsAPI())); + _renderEngine->SetDisablePartialInvalidation(_settings->DisablePartialInvalidation()); _renderEngine->SetSoftwareRendering(_settings->SoftwareRendering()); // Inform the renderer of our opacity _renderEngine->EnableTransparentBackground(_isBackgroundTransparent()); @@ -946,6 +944,21 @@ namespace winrt::Microsoft::Terminal::Control::implementation } } + Control::IControlSettings ControlCore::Settings() + { + return *_settings; + } + + Control::IControlAppearance ControlCore::FocusedAppearance() const + { + return *_settings->FocusedAppearance(); + } + + Control::IControlAppearance ControlCore::UnfocusedAppearance() const + { + return *_settings->UnfocusedAppearance(); + } + void ControlCore::_updateAntiAliasingMode() { D2D1_TEXT_ANTIALIAS_MODE mode; @@ -1010,18 +1023,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation LOG_IF_FAILED(_renderEngine->UpdateFont(_desiredFont, _actualFont, featureMap, axesMap)); } - // If the actual font isn't what was requested... - if (_actualFont.GetFaceName() != _desiredFont.GetFaceName()) - { - // Then warn the user that we picked something because we couldn't find their font. - // Format message with user's choice of font and the font that was chosen instead. - const winrt::hstring message{ fmt::format(std::wstring_view{ RS_(L"NoticeFontNotFound") }, - _desiredFont.GetFaceName(), - _actualFont.GetFaceName()) }; - auto noticeArgs = winrt::make(NoticeLevel::Warning, message); - RaiseNotice.raise(*this, std::move(noticeArgs)); - } - const auto actualNewSize = _actualFont.GetSize(); FontSizeChanged.raise(*this, winrt::make(actualNewSize.width, actualNewSize.height)); } @@ -1710,9 +1711,9 @@ namespace winrt::Microsoft::Terminal::Control::implementation } } - void ControlCore::_rendererWarning(const HRESULT hr) + void ControlCore::_rendererWarning(const HRESULT hr, wil::zwstring_view parameter) { - RendererWarning.raise(*this, winrt::make(hr)); + RendererWarning.raise(*this, winrt::make(hr, winrt::hstring{ parameter })); } winrt::fire_and_forget ControlCore::_renderEngineSwapChainChanged(const HANDLE sourceHandle) @@ -1960,13 +1961,13 @@ namespace winrt::Microsoft::Terminal::Control::implementation UpdateSelectionMarkers.raise(*this, winrt::make(!showMarkers)); } - void ControlCore::AttachUiaEngine(::Microsoft::Console::Render::IRenderEngine* const pEngine) + void ControlCore::AttachUiaEngine(::Microsoft::Console::Render::UiaEngine* const pEngine) { // _renderer will always exist since it's introduced in the ctor const auto lock = _terminal->LockForWriting(); _renderer->AddRenderEngine(pEngine); } - void ControlCore::DetachUiaEngine(::Microsoft::Console::Render::IRenderEngine* const pEngine) + void ControlCore::DetachUiaEngine(::Microsoft::Console::Render::UiaEngine* const pEngine) { const auto lock = _terminal->LockForWriting(); _renderer->RemoveRenderEngine(pEngine); diff --git a/src/cascadia/TerminalControl/ControlCore.h b/src/cascadia/TerminalControl/ControlCore.h index d79df07d1bc..7dbf228cd26 100644 --- a/src/cascadia/TerminalControl/ControlCore.h +++ b/src/cascadia/TerminalControl/ControlCore.h @@ -18,12 +18,22 @@ #include "ControlCore.g.h" #include "SelectionColor.g.h" #include "CommandHistoryContext.g.h" + #include "ControlSettings.h" #include "../../audio/midi/MidiAudio.hpp" -#include "../../renderer/base/Renderer.hpp" +#include "../../buffer/out/search.h" #include "../../cascadia/TerminalCore/Terminal.hpp" -#include "../buffer/out/search.h" -#include "../buffer/out/TextColor.h" +#include "../../renderer/inc/FontInfoDesired.hpp" + +namespace Microsoft::Console::Render::Atlas +{ + class AtlasEngine; +} + +namespace Microsoft::Console::Render +{ + class UiaEngine; +} namespace ControlUnitTests { @@ -82,9 +92,9 @@ namespace winrt::Microsoft::Terminal::Control::implementation void UpdateSettings(const Control::IControlSettings& settings, const IControlAppearance& newAppearance); void ApplyAppearance(const bool& focused); - Control::IControlSettings Settings() { return *_settings; }; - Control::IControlAppearance FocusedAppearance() const { return *_settings->FocusedAppearance(); }; - Control::IControlAppearance UnfocusedAppearance() const { return *_settings->UnfocusedAppearance(); }; + Control::IControlSettings Settings(); + Control::IControlAppearance FocusedAppearance() const; + Control::IControlAppearance UnfocusedAppearance() const; bool HasUnfocusedAppearance() const; winrt::Microsoft::Terminal::Core::Scheme ColorScheme() const noexcept; @@ -219,8 +229,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation const bool isOnOriginalPosition, bool& selectionNeedsToBeCopied); - void AttachUiaEngine(::Microsoft::Console::Render::IRenderEngine* const pEngine); - void DetachUiaEngine(::Microsoft::Console::Render::IRenderEngine* const pEngine); + void AttachUiaEngine(::Microsoft::Console::Render::UiaEngine* const pEngine); + void DetachUiaEngine(::Microsoft::Console::Render::UiaEngine* const pEngine); bool IsInReadOnlyMode() const; void ToggleReadOnlyMode(); @@ -306,7 +316,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation // As _renderer has a dependency on _renderEngine (through a raw pointer) // we must ensure the _renderer is deallocated first. // (C++ class members are destroyed in reverse order.) - std::unique_ptr<::Microsoft::Console::Render::IRenderEngine> _renderEngine{ nullptr }; + std::unique_ptr<::Microsoft::Console::Render::Atlas::AtlasEngine> _renderEngine{ nullptr }; std::unique_ptr<::Microsoft::Console::Render::Renderer> _renderer{ nullptr }; ::Search _searcher; @@ -380,7 +390,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation winrt::Windows::System::DispatcherQueueTimer _midiAudioSkipTimer{ nullptr }; #pragma region RendererCallbacks - void _rendererWarning(const HRESULT hr); + void _rendererWarning(const HRESULT hr, wil::zwstring_view parameter); winrt::fire_and_forget _renderEngineSwapChainChanged(const HANDLE handle); void _rendererBackgroundColorChanged(); void _rendererTabColorChanged(); diff --git a/src/cascadia/TerminalControl/EventArgs.h b/src/cascadia/TerminalControl/EventArgs.h index 1c135cdf4f6..2ed2c2b828b 100644 --- a/src/cascadia/TerminalControl/EventArgs.h +++ b/src/cascadia/TerminalControl/EventArgs.h @@ -148,12 +148,14 @@ namespace winrt::Microsoft::Terminal::Control::implementation struct RendererWarningArgs : public RendererWarningArgsT { public: - RendererWarningArgs(const uint64_t hr) : - _Result(hr) + RendererWarningArgs(const HRESULT hr, winrt::hstring parameter) : + _Result{ hr }, + _Parameter{ std::move(parameter) } { } - WINRT_PROPERTY(uint64_t, Result); + WINRT_PROPERTY(HRESULT, Result); + WINRT_PROPERTY(winrt::hstring, Parameter); }; struct TransparencyChangedEventArgs : public TransparencyChangedEventArgsT diff --git a/src/cascadia/TerminalControl/EventArgs.idl b/src/cascadia/TerminalControl/EventArgs.idl index 3caea5a0f38..92784fc6414 100644 --- a/src/cascadia/TerminalControl/EventArgs.idl +++ b/src/cascadia/TerminalControl/EventArgs.idl @@ -11,6 +11,12 @@ namespace Microsoft.Terminal.Control All = 0xffffffff }; + enum GraphicsAPI + { + Automatic, + Direct2D, + Direct3D11, + }; runtimeclass FontSizeChangedArgs { @@ -71,7 +77,8 @@ namespace Microsoft.Terminal.Control runtimeclass RendererWarningArgs { - UInt64 Result { get; }; + HRESULT Result { get; }; + String Parameter { get; }; } runtimeclass TransparencyChangedEventArgs @@ -90,7 +97,7 @@ namespace Microsoft.Terminal.Control { Boolean ShowOrHide { get; }; } - + runtimeclass UpdateSelectionMarkersEventArgs { Boolean ClearMarkers { get; }; diff --git a/src/cascadia/TerminalControl/HwndTerminal.cpp b/src/cascadia/TerminalControl/HwndTerminal.cpp index c34e5b4523f..bd05b7ca8a2 100644 --- a/src/cascadia/TerminalControl/HwndTerminal.cpp +++ b/src/cascadia/TerminalControl/HwndTerminal.cpp @@ -217,7 +217,6 @@ HRESULT HwndTerminal::Initialize() auto engine = std::make_unique<::Microsoft::Console::Render::AtlasEngine>(); RETURN_IF_FAILED(engine->SetHwnd(_hwnd.get())); - RETURN_IF_FAILED(engine->Enable()); _renderer->AddRenderEngine(engine.get()); _UpdateFont(USER_DEFAULT_SCREEN_DPI); diff --git a/src/cascadia/TerminalControl/IControlSettings.idl b/src/cascadia/TerminalControl/IControlSettings.idl index 63c35d8a952..2db92a86a83 100644 --- a/src/cascadia/TerminalControl/IControlSettings.idl +++ b/src/cascadia/TerminalControl/IControlSettings.idl @@ -58,7 +58,8 @@ namespace Microsoft.Terminal.Control TextAntialiasingMode AntialiasingMode { get; }; // Experimental Settings - Boolean ForceFullRepaintRendering { get; }; + Microsoft.Terminal.Control.GraphicsAPI GraphicsAPI { get; }; + Boolean DisablePartialInvalidation { get; }; Boolean SoftwareRendering { get; }; Boolean ShowMarks { get; }; Boolean UseBackgroundImageForWindow { get; }; diff --git a/src/cascadia/TerminalControl/Resources/en-US/Resources.resw b/src/cascadia/TerminalControl/Resources/en-US/Resources.resw index a604fc912e4..02245a01577 100644 --- a/src/cascadia/TerminalControl/Resources/en-US/Resources.resw +++ b/src/cascadia/TerminalControl/Resources/en-US/Resources.resw @@ -213,6 +213,14 @@ Please either install the missing font or choose another one. Renderer encountered an unexpected error: {0} {0} is an error code. + + Unable to find the following fonts: {0}. Please either install them or choose different fonts. + {Locked="{0}"} This is a warning dialog shown when the user selects a font that isn't installed. + + + Renderer encountered an unexpected error: {0:#010x} {1} + {Locked="{0:#010x}","{1}"} {0:#010x} is a placeholder for a Windows error code (e.g. 0x88985002). {1} is the corresponding message. + Read-only mode is enabled. diff --git a/src/cascadia/TerminalControl/TermControl.cpp b/src/cascadia/TerminalControl/TermControl.cpp index de8ec920d3e..303d6d61482 100644 --- a/src/cascadia/TerminalControl/TermControl.cpp +++ b/src/cascadia/TerminalControl/TermControl.cpp @@ -991,36 +991,45 @@ namespace winrt::Microsoft::Terminal::Control::implementation // - hr: an HRESULT describing the warning // Return Value: // - - winrt::fire_and_forget TermControl::_RendererWarning(IInspectable /*sender*/, - Control::RendererWarningArgs args) + winrt::fire_and_forget TermControl::_RendererWarning(IInspectable /*sender*/, Control::RendererWarningArgs args) { - const auto hr = static_cast(args.Result()); - auto weakThis{ get_weak() }; co_await wil::resume_foreground(Dispatcher()); - if (auto control{ weakThis.get() }) + const auto control = weakThis.get(); + if (!control) { - winrt::hstring message; - if (HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND) == hr || - HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND) == hr) - { - message = winrt::hstring{ fmt::format(std::wstring_view{ RS_(L"PixelShaderNotFound") }, - (_focused ? _core.FocusedAppearance() : _core.UnfocusedAppearance()).PixelShaderPath()) }; - } - else if (D2DERR_SHADER_COMPILE_FAILED == hr) - { - message = winrt::hstring{ fmt::format(std::wstring_view{ RS_(L"PixelShaderCompileFailed") }) }; - } - else - { - message = winrt::hstring{ fmt::format(std::wstring_view{ RS_(L"UnexpectedRendererError") }, - hr) }; - } + co_return; + } - auto noticeArgs = winrt::make(NoticeLevel::Warning, std::move(message)); - control->RaiseNotice.raise(*control, std::move(noticeArgs)); + const auto hr = args.Result(); + const auto parameter = args.Parameter(); + winrt::hstring message; + + switch (hr) + { + case HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND): + case HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND): + message = winrt::hstring{ fmt::format(std::wstring_view{ RS_(L"PixelShaderNotFound") }, parameter) }; + break; + case D2DERR_SHADER_COMPILE_FAILED: + message = winrt::hstring{ fmt::format(std::wstring_view{ RS_(L"PixelShaderCompileFailed") }) }; + break; + case DWRITE_E_NOFONT: + message = winrt::hstring{ fmt::format(std::wstring_view{ RS_(L"RendererErrorFontNotFound") }, parameter) }; + break; + default: + { + wchar_t buf[512]; + const auto len = FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, nullptr, hr, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), &buf[0], ARRAYSIZE(buf), nullptr); + const std::wstring_view msg{ &buf[0], len }; + message = winrt::hstring{ fmt::format(std::wstring_view{ RS_(L"RendererErrorOther") }, hr, msg) }; + break; } + } + + auto noticeArgs = winrt::make(NoticeLevel::Warning, std::move(message)); + control->RaiseNotice.raise(*control, std::move(noticeArgs)); } void TermControl::_AttachDxgiSwapChainToXaml(HANDLE swapChainHandle) @@ -2409,7 +2418,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation LOG_IF_FAILED(engine->UpdateDpi(dpi)); LOG_IF_FAILED(engine->UpdateFont(desiredFont, actualFont)); - const auto scale = engine->GetScaling(); + const auto scale = dpi / static_cast(USER_DEFAULT_SCREEN_DPI); const auto actualFontSize = actualFont.GetSize(); // UWP XAML scrollbars aren't guaranteed to be the same size as the diff --git a/src/cascadia/TerminalSettingsEditor/Appearances.cpp b/src/cascadia/TerminalSettingsEditor/Appearances.cpp index 85099de045d..fe9cabc824f 100644 --- a/src/cascadia/TerminalSettingsEditor/Appearances.cpp +++ b/src/cascadia/TerminalSettingsEditor/Appearances.cpp @@ -6,8 +6,8 @@ #include "Appearances.g.cpp" #include "AxisKeyValuePair.g.cpp" #include "FeatureKeyValuePair.g.cpp" -#include "EnumEntry.h" +#include "EnumEntry.h" #include #include "..\WinRTUtils\inc\Utils.h" @@ -36,28 +36,6 @@ static constexpr std::array DefaultFeatures{ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation { - bool Font::HasPowerlineCharacters() - { - if (!_hasPowerlineCharacters.has_value()) - { - try - { - winrt::com_ptr font; - THROW_IF_FAILED(_family->GetFont(0, font.put())); - BOOL exists{}; - // We're actually checking for the "Extended" PowerLine glyph set. - // They're more fun. - THROW_IF_FAILED(font->HasCharacter(0xE0B6, &exists)); - _hasPowerlineCharacters = (exists == TRUE); - } - catch (...) - { - _hasPowerlineCharacters = false; - } - } - return _hasPowerlineCharacters.value_or(false); - } - Windows::Foundation::Collections::IMap Font::FontAxesTagsAndNames() { if (!_fontAxesTagsAndNames) @@ -370,6 +348,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation } }); + _refreshFontFaceDependents(); InitializeFontAxesVector(); InitializeFontFeaturesVector(); @@ -382,7 +361,119 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation } } - double AppearanceViewModel::LineHeight() const noexcept + winrt::hstring AppearanceViewModel::FontFace() const + { + return _appearance.SourceProfile().FontInfo().FontFace(); + } + + void AppearanceViewModel::FontFace(const winrt::hstring& value) + { + const auto fontInfo = _appearance.SourceProfile().FontInfo(); + if (fontInfo.FontFace() == value) + { + return; + } + + fontInfo.FontFace(value); + _refreshFontFaceDependents(); + + _NotifyChanges(L"HasFontFace", L"FontFace"); + } + + bool AppearanceViewModel::HasFontFace() const + { + return _appearance.SourceProfile().FontInfo().HasFontFace(); + } + + void AppearanceViewModel::ClearFontFace() + { + const auto fontInfo = _appearance.SourceProfile().FontInfo(); + const auto hadValue = fontInfo.HasFontFace(); + + fontInfo.ClearFontFace(); + _refreshFontFaceDependents(); + + if (hadValue) + { + _NotifyChanges(L"HasFontFace", L"FontFace"); + } + } + + Model::FontConfig AppearanceViewModel::FontFaceOverrideSource() const + { + return _appearance.SourceProfile().FontInfo().FontFaceOverrideSource(); + } + + void AppearanceViewModel::_refreshFontFaceDependents() + { + wil::com_ptr factory; + THROW_IF_FAILED(DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED, __uuidof(factory), reinterpret_cast<::IUnknown**>(factory.addressof()))); + + wil::com_ptr fontCollection; + THROW_IF_FAILED(factory->GetSystemFontCollection(fontCollection.addressof(), FALSE)); + + const auto fontFace = FontFace(); + std::wstring primaryFontName; + std::wstring missingFonts; + std::wstring proportionalFonts; + BOOL hasPowerlineCharacters = FALSE; + + til::iterate_font_families(fontFace, [&](wil::zwstring_view name) { + std::wstring* accumulator = nullptr; + + UINT32 index = 0; + BOOL exists = FALSE; + THROW_IF_FAILED(fontCollection->FindFamilyName(name.c_str(), &index, &exists)); + + // Look ma, no goto! + do + { + if (!exists) + { + accumulator = &missingFonts; + break; + } + + if (primaryFontName.empty()) + { + primaryFontName = name; + } + + wil::com_ptr fontFamily; + THROW_IF_FAILED(fontCollection->GetFontFamily(index, fontFamily.addressof())); + + wil::com_ptr font; + THROW_IF_FAILED(fontFamily->GetFirstMatchingFont(DWRITE_FONT_WEIGHT_NORMAL, DWRITE_FONT_STRETCH_NORMAL, DWRITE_FONT_STYLE_NORMAL, font.addressof())); + + if (!font.query()->IsMonospacedFont()) + { + accumulator = &proportionalFonts; + } + + // We're actually checking for the "Extended" PowerLine glyph set. + // They're more fun. + BOOL hasE0B6 = FALSE; + std::ignore = font->HasCharacter(0xE0B6, &hasE0B6); + hasPowerlineCharacters |= hasE0B6; + } while (false); + + if (accumulator) + { + if (!accumulator->empty()) + { + accumulator->append(L", "); + } + accumulator->append(name); + } + }); + + _primaryFontName = std::move(primaryFontName); + MissingFontFaces(winrt::hstring{ missingFonts }); + ProportionalFontFaces(winrt::hstring{ proportionalFonts }); + HasPowerlineCharacters(hasPowerlineCharacters); + } + + double AppearanceViewModel::LineHeight() const { const auto fontInfo = _appearance.SourceProfile().FontInfo(); const auto cellHeight = fontInfo.CellHeight(); @@ -526,7 +617,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation // find one axis that does not already exist, and add that // if there are no more possible axes to add, the button is disabled so there shouldn't be a way to get here - const auto possibleAxesTagsAndNames = ProfileViewModel::FindFontWithLocalizedName(FontFace()).FontAxesTagsAndNames(); + const auto possibleAxesTagsAndNames = ProfileViewModel::FindFontWithLocalizedName(_primaryFontName).FontAxesTagsAndNames(); for (const auto tagAndName : possibleAxesTagsAndNames) { if (!fontAxesMap.HasKey(tagAndName.Key())) @@ -567,7 +658,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation _FontAxesVector.Clear(); if (const auto fontAxesMap = _appearance.SourceProfile().FontInfo().FontAxes()) { - const auto fontAxesTagToNameMap = ProfileViewModel::FindFontWithLocalizedName(FontFace()).FontAxesTagsAndNames(); + const auto fontAxesTagToNameMap = ProfileViewModel::FindFontWithLocalizedName(_primaryFontName).FontAxesTagsAndNames(); for (const auto axis : fontAxesMap) { // only show the axes that the font supports @@ -585,14 +676,14 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation // - Determines whether the currently selected font has any variable font axes bool AppearanceViewModel::AreFontAxesAvailable() { - return ProfileViewModel::FindFontWithLocalizedName(FontFace()).FontAxesTagsAndNames().Size() > 0; + return ProfileViewModel::FindFontWithLocalizedName(_primaryFontName).FontAxesTagsAndNames().Size() > 0; } // Method Description: // - Determines whether the currently selected font has any variable font axes that have not already been set bool AppearanceViewModel::CanFontAxesBeAdded() { - if (const auto fontAxesTagToNameMap = ProfileViewModel::FindFontWithLocalizedName(FontFace()).FontAxesTagsAndNames(); fontAxesTagToNameMap.Size() > 0) + if (const auto fontAxesTagToNameMap = ProfileViewModel::FindFontWithLocalizedName(_primaryFontName).FontAxesTagsAndNames(); fontAxesTagToNameMap.Size() > 0) { if (const auto fontAxesMap = _appearance.SourceProfile().FontInfo().FontAxes()) { @@ -641,7 +732,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation // find one feature that does not already exist, and add that // if there are no more possible features to add, the button is disabled so there shouldn't be a way to get here - const auto possibleFeaturesTagsAndNames = ProfileViewModel::FindFontWithLocalizedName(FontFace()).FontFeaturesTagsAndNames(); + const auto possibleFeaturesTagsAndNames = ProfileViewModel::FindFontWithLocalizedName(_primaryFontName).FontFeaturesTagsAndNames(); for (const auto tagAndName : possibleFeaturesTagsAndNames) { const auto featureKey = tagAndName.Key(); @@ -684,7 +775,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation _FontFeaturesVector.Clear(); if (const auto fontFeaturesMap = _appearance.SourceProfile().FontInfo().FontFeatures()) { - const auto fontFeaturesTagToNameMap = ProfileViewModel::FindFontWithLocalizedName(FontFace()).FontFeaturesTagsAndNames(); + const auto fontFeaturesTagToNameMap = ProfileViewModel::FindFontWithLocalizedName(_primaryFontName).FontFeaturesTagsAndNames(); for (const auto feature : fontFeaturesMap) { const auto featureKey = feature.Key(); @@ -703,14 +794,14 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation // - Determines whether the currently selected font has any font features bool AppearanceViewModel::AreFontFeaturesAvailable() { - return ProfileViewModel::FindFontWithLocalizedName(FontFace()).FontFeaturesTagsAndNames().Size() > 0; + return ProfileViewModel::FindFontWithLocalizedName(_primaryFontName).FontFeaturesTagsAndNames().Size() > 0; } // Method Description: // - Determines whether the currently selected font has any font features that have not already been set bool AppearanceViewModel::CanFontFeaturesBeAdded() { - if (const auto fontFeaturesTagToNameMap = ProfileViewModel::FindFontWithLocalizedName(FontFace()).FontFeaturesTagsAndNames(); fontFeaturesTagToNameMap.Size() > 0) + if (const auto fontFeaturesTagToNameMap = ProfileViewModel::FindFontWithLocalizedName(_primaryFontName).FontFeaturesTagsAndNames(); fontFeaturesTagToNameMap.Size() > 0) { if (const auto fontFeaturesMap = _appearance.SourceProfile().FontInfo().FontFeatures()) { @@ -775,9 +866,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation DependencyProperty Appearances::_AppearanceProperty{ nullptr }; - Appearances::Appearances() : - _ShowAllFonts{ false }, - _ShowProportionalFontWarning{ false } + Appearances::Appearances() { InitializeComponent(); @@ -848,61 +937,58 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation INITIALIZE_BINDABLE_ENUM_SETTING(IntenseTextStyle, IntenseTextStyle, winrt::Microsoft::Terminal::Settings::Model::IntenseStyle, L"Appearance_IntenseTextStyle", L"Content"); } - // Method Description: - // - Searches through our list of monospace fonts to determine if the settings model's current font face is a monospace font - bool Appearances::UsingMonospaceFont() const noexcept + IObservableVector Appearances::FilteredFontList() { - auto result{ false }; - const auto currentFont{ Appearance().FontFace() }; - for (const auto& font : ProfileViewModel::MonospaceFontList()) + if (!_filteredFonts) { - if (font.LocalizedName() == currentFont) - { - result = true; - } + _updateFilteredFontList(); } - return result; + return _filteredFonts; } // Method Description: // - Determines whether we should show the list of all the fonts, or we should just show monospace fonts bool Appearances::ShowAllFonts() const noexcept { - // - _ShowAllFonts is directly bound to the checkbox. So this is the user set value. - // - If we are not using a monospace font, show all of the fonts so that the ComboBox is still properly bound - return _ShowAllFonts || !UsingMonospaceFont(); + return _ShowAllFonts; } - void Appearances::ShowAllFonts(const bool& value) + void Appearances::ShowAllFonts(const bool value) { if (_ShowAllFonts != value) { _ShowAllFonts = value; + _filteredFonts = nullptr; PropertyChanged.raise(*this, PropertyChangedEventArgs{ L"ShowAllFonts" }); + PropertyChanged.raise(*this, PropertyChangedEventArgs{ L"FilteredFontList" }); } } - IInspectable Appearances::CurrentFontFace() const + void Appearances::FontFaceBox_GotFocus(const Windows::Foundation::IInspectable& sender, const RoutedEventArgs&) { - const auto& appearanceVM{ Appearance() }; - const auto appearanceFontFace{ appearanceVM.FontFace() }; - return box_value(ProfileViewModel::FindFontWithLocalizedName(appearanceFontFace)); + _updateFontNameFilter({}); + sender.as().IsSuggestionListOpen(true); } - void Appearances::FontFace_SelectionChanged(const IInspectable& /*sender*/, const SelectionChangedEventArgs& e) + void Appearances::FontFaceBox_LostFocus(const IInspectable& sender, const RoutedEventArgs&) { - // NOTE: We need to hook up a selection changed event handler here instead of directly binding to the appearance view model. - // A two way binding to the view model causes an infinite loop because both combo boxes keep fighting over which one's right. - const auto selectedItem{ e.AddedItems().GetAt(0) }; - const auto newFontFace{ unbox_value(selectedItem) }; - Appearance().FontFace(newFontFace.LocalizedName()); - if (!UsingMonospaceFont()) + const auto appearance = Appearance(); + const auto fontSpec = sender.as().Text(); + + if (fontSpec.empty()) { - ShowProportionalFontWarning(true); + appearance.ClearFontFace(); } else { - ShowProportionalFontWarning(false); + appearance.FontFace(fontSpec); + } + + // TODO: Any use of FindFontWithLocalizedName is broken and requires refactoring in time for version 1.21. + const auto newFontFace = ProfileViewModel::FindFontWithLocalizedName(fontSpec); + if (!newFontFace) + { + return; } _FontAxesNames.Clear(); @@ -948,6 +1034,89 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation } } + void Appearances::FontFaceBox_SuggestionChosen(const AutoSuggestBox& sender, const AutoSuggestBoxSuggestionChosenEventArgs& args) + { + const auto font = unbox_value(args.SelectedItem()); + const auto fontName = font.Name(); + auto fontSpec = sender.Text(); + + const std::wstring_view fontSpecView{ fontSpec }; + if (const auto idx = fontSpecView.rfind(L','); idx != std::wstring_view::npos) + { + const auto prefix = fontSpecView.substr(0, idx); + const auto suffix = std::wstring_view{ fontName }; + fontSpec = winrt::hstring{ fmt::format(FMT_COMPILE(L"{}, {}"), prefix, suffix) }; + } + else + { + fontSpec = fontName; + } + + sender.Text(fontSpec); + } + + void Appearances::FontFaceBox_TextChanged(const AutoSuggestBox& sender, const AutoSuggestBoxTextChangedEventArgs& args) + { + if (args.Reason() != AutoSuggestionBoxTextChangeReason::UserInput) + { + return; + } + + const auto fontSpec = sender.Text(); + std::wstring_view filter{ fontSpec }; + + // Find the last font name in the font, spec, list. + if (const auto idx = filter.rfind(L','); idx != std::wstring_view::npos) + { + filter = filter.substr(idx + 1); + } + + filter = til::trim(filter, L' '); + _updateFontNameFilter(filter); + } + + void Appearances::_updateFontNameFilter(std::wstring_view filter) + { + if (_fontNameFilter != filter) + { + _filteredFonts = nullptr; + _fontNameFilter = filter; + PropertyChanged.raise(*this, PropertyChangedEventArgs{ L"FilteredFontList" }); + } + } + + void Appearances::_updateFilteredFontList() + { + _filteredFonts = _ShowAllFonts ? ProfileViewModel::CompleteFontList() : ProfileViewModel::MonospaceFontList(); + + if (_fontNameFilter.empty()) + { + return; + } + + std::vector filtered; + filtered.reserve(_filteredFonts.Size()); + + for (const auto& font : _filteredFonts) + { + const auto name = font.Name(); + bool match = til::contains_linguistic_insensitive(name, _fontNameFilter); + + if (!match) + { + const auto localizedName = font.LocalizedName(); + match = localizedName != name && til::contains_linguistic_insensitive(localizedName, _fontNameFilter); + } + + if (match) + { + filtered.emplace_back(font); + } + } + + _filteredFonts = winrt::single_threaded_observable_vector(std::move(filtered)); + } + void Appearances::_ViewModelChanged(const DependencyObject& d, const DependencyPropertyChangedEventArgs& /*args*/) { const auto& obj{ d.as() }; @@ -956,21 +1125,21 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation void Appearances::_UpdateWithNewViewModel() { - if (Appearance()) + if (const auto appearance = Appearance()) { - const auto& biAlignmentVal{ static_cast(Appearance().BackgroundImageAlignment()) }; + const auto& biAlignmentVal{ static_cast(appearance.BackgroundImageAlignment()) }; for (const auto& biButton : _BIAlignmentButtons) { biButton.IsChecked(biButton.Tag().as() == biAlignmentVal); } FontAxesCVS().Source(Appearance().FontAxesVector()); - Appearance().AreFontAxesAvailable() ? FontAxesContainer().HelpText(RS_(L"Profile_FontAxesAvailable/Text")) : FontAxesContainer().HelpText(RS_(L"Profile_FontAxesUnavailable/Text")); + FontAxesContainer().HelpText(appearance.AreFontAxesAvailable() ? RS_(L"Profile_FontAxesAvailable/Text") : RS_(L"Profile_FontAxesUnavailable/Text")); FontFeaturesCVS().Source(Appearance().FontFeaturesVector()); - Appearance().AreFontFeaturesAvailable() ? FontFeaturesContainer().HelpText(RS_(L"Profile_FontFeaturesAvailable/Text")) : FontFeaturesContainer().HelpText(RS_(L"Profile_FontFeaturesUnavailable/Text")); + FontFeaturesContainer().HelpText(appearance.AreFontFeaturesAvailable() ? RS_(L"Profile_FontFeaturesAvailable/Text") : RS_(L"Profile_FontFeaturesUnavailable/Text")); - _ViewModelChangedRevoker = Appearance().PropertyChanged(winrt::auto_revoke, [=](auto&&, const PropertyChangedEventArgs& args) { + _ViewModelChangedRevoker = appearance.PropertyChanged(winrt::auto_revoke, [=](auto&&, const PropertyChangedEventArgs& args) { const auto settingName{ args.PropertyName() }; if (settingName == L"CursorShape") { @@ -987,24 +1156,13 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation } else if (settingName == L"BackgroundImageAlignment") { - _UpdateBIAlignmentControl(static_cast(Appearance().BackgroundImageAlignment())); + _UpdateBIAlignmentControl(static_cast(appearance.BackgroundImageAlignment())); } else if (settingName == L"FontWeight") { PropertyChanged.raise(*this, PropertyChangedEventArgs{ L"CurrentFontWeight" }); PropertyChanged.raise(*this, PropertyChangedEventArgs{ L"IsCustomFontWeight" }); } - else if (settingName == L"FontFace" || settingName == L"CurrentFontList") - { - // notify listener that all font face related values might have changed - if (!UsingMonospaceFont()) - { - _ShowAllFonts = true; - } - PropertyChanged.raise(*this, PropertyChangedEventArgs{ L"CurrentFontFace" }); - PropertyChanged.raise(*this, PropertyChangedEventArgs{ L"ShowAllFonts" }); - PropertyChanged.raise(*this, PropertyChangedEventArgs{ L"UsingMonospaceFont" }); - } else if (settingName == L"IntenseTextStyle") { PropertyChanged.raise(*this, PropertyChangedEventArgs{ L"CurrentIntenseTextStyle" }); @@ -1040,12 +1198,10 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation PropertyChanged.raise(*this, PropertyChangedEventArgs{ L"IsVintageCursor" }); PropertyChanged.raise(*this, PropertyChangedEventArgs{ L"CurrentColorScheme" }); PropertyChanged.raise(*this, PropertyChangedEventArgs{ L"CurrentBackgroundImageStretchMode" }); - _UpdateBIAlignmentControl(static_cast(Appearance().BackgroundImageAlignment())); + _UpdateBIAlignmentControl(static_cast(appearance.BackgroundImageAlignment())); PropertyChanged.raise(*this, PropertyChangedEventArgs{ L"CurrentFontWeight" }); PropertyChanged.raise(*this, PropertyChangedEventArgs{ L"IsCustomFontWeight" }); - PropertyChanged.raise(*this, PropertyChangedEventArgs{ L"CurrentFontFace" }); PropertyChanged.raise(*this, PropertyChangedEventArgs{ L"ShowAllFonts" }); - PropertyChanged.raise(*this, PropertyChangedEventArgs{ L"UsingMonospaceFont" }); PropertyChanged.raise(*this, PropertyChangedEventArgs{ L"CurrentIntenseTextStyle" }); PropertyChanged.raise(*this, PropertyChangedEventArgs{ L"CurrentAdjustIndistinguishableColors" }); PropertyChanged.raise(*this, PropertyChangedEventArgs{ L"ShowProportionalFontWarning" }); diff --git a/src/cascadia/TerminalSettingsEditor/Appearances.h b/src/cascadia/TerminalSettingsEditor/Appearances.h index 72270cd78d7..d01819b24b5 100644 --- a/src/cascadia/TerminalSettingsEditor/Appearances.h +++ b/src/cascadia/TerminalSettingsEditor/Appearances.h @@ -31,15 +31,14 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation struct Font : FontT { public: - Font(winrt::hstring name, winrt::hstring localizedName, IDWriteFontFamily* family) : + Font(winrt::hstring name, winrt::hstring localizedName, wil::com_ptr family) : _Name{ std::move(name) }, - _LocalizedName{ std::move(localizedName) } + _LocalizedName{ std::move(localizedName) }, + _family{ std::move(family) } { - _family.copy_from(family); } hstring ToString() { return _LocalizedName; } - bool HasPowerlineCharacters(); Windows::Foundation::Collections::IMap FontAxesTagsAndNames(); Windows::Foundation::Collections::IMap FontFeaturesTagsAndNames(); @@ -47,14 +46,12 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation WINRT_PROPERTY(hstring, LocalizedName); private: - winrt::com_ptr _family; - std::optional _hasPowerlineCharacters; - winrt::hstring _tagToString(DWRITE_FONT_AXIS_TAG tag); winrt::hstring _tagToString(DWRITE_FONT_FEATURE_TAG tag); Windows::Foundation::Collections::IMap _fontAxesTagsAndNames; Windows::Foundation::Collections::IMap _fontFeaturesTagsAndNames; + wil::com_ptr _family; }; struct AxisKeyValuePair : AxisKeyValuePairT, ViewModelHelper @@ -108,11 +105,18 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation public: AppearanceViewModel(const Model::AppearanceConfig& appearance); - double LineHeight() const noexcept; + winrt::hstring FontFace() const; + void FontFace(const winrt::hstring& value); + bool HasFontFace() const; + void ClearFontFace(); + Model::FontConfig FontFaceOverrideSource() const; + + double LineHeight() const; void LineHeight(const double value); bool HasLineHeight() const; void ClearLineHeight(); Model::FontConfig LineHeightOverrideSource() const; + void SetFontWeightFromDouble(double fontWeight); void SetBackgroundImageOpacityFromPercentageValue(double percentageValue); void SetBackgroundImagePath(winrt::hstring path); @@ -139,13 +143,13 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation bool CanFontFeaturesBeAdded(); WINRT_PROPERTY(bool, IsDefault, false); + WINRT_PROPERTY(bool, HasPowerlineCharacters, false); // These settings are not defined in AppearanceConfig, so we grab them // from the source profile itself. The reason we still want them in the // AppearanceViewModel is so we can continue to have the 'Text' grouping // we currently have in xaml, since that grouping has some settings that // are defined in AppearanceConfig and some that are not. - OBSERVABLE_PROJECTED_SETTING(_appearance.SourceProfile().FontInfo(), FontFace); OBSERVABLE_PROJECTED_SETTING(_appearance.SourceProfile().FontInfo(), FontSize); OBSERVABLE_PROJECTED_SETTING(_appearance.SourceProfile().FontInfo(), FontWeight); OBSERVABLE_PROJECTED_SETTING(_appearance.SourceProfile().FontInfo(), FontAxes); @@ -165,37 +169,43 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation OBSERVABLE_PROJECTED_SETTING(_appearance, IntenseTextStyle); OBSERVABLE_PROJECTED_SETTING(_appearance, AdjustIndistinguishableColors); WINRT_OBSERVABLE_PROPERTY(Windows::Foundation::Collections::IObservableVector, SchemesList, _propertyChangedHandlers, nullptr); + WINRT_OBSERVABLE_PROPERTY(winrt::hstring, MissingFontFaces, _propertyChangedHandlers); + WINRT_OBSERVABLE_PROPERTY(winrt::hstring, ProportionalFontFaces, _propertyChangedHandlers); WINRT_OBSERVABLE_PROPERTY(Windows::Foundation::Collections::IObservableVector, FontAxesVector, _propertyChangedHandlers, nullptr); WINRT_OBSERVABLE_PROPERTY(Windows::Foundation::Collections::IObservableVector, FontFeaturesVector, _propertyChangedHandlers, nullptr); private: - Model::AppearanceConfig _appearance; - winrt::hstring _lastBgImagePath; - + void _refreshFontFaceDependents(); Editor::AxisKeyValuePair _CreateAxisKeyValuePairHelper(winrt::hstring axisKey, float axisValue, const Windows::Foundation::Collections::IMap& baseMap, const Windows::Foundation::Collections::IMap& tagToNameMap); Editor::FeatureKeyValuePair _CreateFeatureKeyValuePairHelper(winrt::hstring axisKey, uint32_t axisValue, const Windows::Foundation::Collections::IMap& baseMap, const Windows::Foundation::Collections::IMap& tagToNameMap); - bool _IsDefaultFeature(winrt::hstring featureTag); + + Model::AppearanceConfig _appearance; + winrt::hstring _lastBgImagePath; + std::wstring _primaryFontName; }; struct Appearances : AppearancesT { public: - Appearances(); + static winrt::hstring FontAxisName(const winrt::hstring& key); + static winrt::hstring FontFeatureName(const winrt::hstring& key); - // font face - Windows::Foundation::IInspectable CurrentFontFace() const; + Appearances(); // CursorShape visibility logic bool IsVintageCursor() const; - bool UsingMonospaceFont() const noexcept; + Windows::Foundation::Collections::IObservableVector FilteredFontList(); bool ShowAllFonts() const noexcept; - void ShowAllFonts(const bool& value); + void ShowAllFonts(bool value); + void FontFaceBox_GotFocus(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& e); + void FontFaceBox_LostFocus(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& e); + void FontFaceBox_SuggestionChosen(const winrt::Windows::UI::Xaml::Controls::AutoSuggestBox&, const winrt::Windows::UI::Xaml::Controls::AutoSuggestBoxSuggestionChosenEventArgs&); + void FontFaceBox_TextChanged(const winrt::Windows::UI::Xaml::Controls::AutoSuggestBox&, const winrt::Windows::UI::Xaml::Controls::AutoSuggestBoxTextChangedEventArgs&); fire_and_forget BackgroundImage_Click(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& e); void BIAlignment_Click(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& e); - void FontFace_SelectionChanged(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::Controls::SelectionChangedEventArgs& e); void DeleteAxisKeyValuePair_Click(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& e); void AddNewAxisKeyValuePair_Click(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& e); void DeleteFeatureKeyValuePair_Click(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& e); @@ -219,21 +229,23 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation GETSET_BINDABLE_ENUM_SETTING(BackgroundImageStretchMode, Windows::UI::Xaml::Media::Stretch, Appearance().BackgroundImageStretchMode); GETSET_BINDABLE_ENUM_SETTING(IntenseTextStyle, Microsoft::Terminal::Settings::Model::IntenseStyle, Appearance().IntenseTextStyle); - WINRT_OBSERVABLE_PROPERTY(bool, ShowProportionalFontWarning, PropertyChanged.raise, nullptr); private: - bool _ShowAllFonts; - void _UpdateBIAlignmentControl(const int32_t val); + Windows::UI::Xaml::Data::INotifyPropertyChanged::PropertyChanged_revoker _ViewModelChangedRevoker; std::array _BIAlignmentButtons; - Windows::Foundation::Collections::IMap _FontWeightMap; Editor::EnumEntry _CustomFontWeight{ nullptr }; - + Windows::Foundation::Collections::IObservableVector _filteredFonts; Windows::Foundation::Collections::IObservableVector _FontAxesNames; Windows::Foundation::Collections::IObservableVector _FontFeaturesNames; + std::wstring _fontNameFilter; + bool _ShowAllFonts = false; - Windows::UI::Xaml::Data::INotifyPropertyChanged::PropertyChanged_revoker _ViewModelChangedRevoker; static void _ViewModelChanged(const Windows::UI::Xaml::DependencyObject& d, const Windows::UI::Xaml::DependencyPropertyChangedEventArgs& e); + + void _updateFontNameFilter(std::wstring_view filter); + void _updateFilteredFontList(); + void _UpdateBIAlignmentControl(const int32_t val); void _UpdateWithNewViewModel(); }; }; diff --git a/src/cascadia/TerminalSettingsEditor/Appearances.idl b/src/cascadia/TerminalSettingsEditor/Appearances.idl index 45f07acaa44..88fb2c6f898 100644 --- a/src/cascadia/TerminalSettingsEditor/Appearances.idl +++ b/src/cascadia/TerminalSettingsEditor/Appearances.idl @@ -21,7 +21,6 @@ namespace Microsoft.Terminal.Settings.Editor { String Name { get; }; String LocalizedName { get; }; - Boolean HasPowerlineCharacters { get; }; Windows.Foundation.Collections.IMap FontAxesTagsAndNames { get; }; Windows.Foundation.Collections.IMap FontFeaturesTagsAndNames { get; }; } @@ -59,6 +58,10 @@ namespace Microsoft.Terminal.Settings.Editor ColorSchemeViewModel CurrentColorScheme; Windows.Foundation.Collections.IObservableVector SchemesList; + String MissingFontFaces { get; }; + String ProportionalFontFaces { get; }; + Boolean HasPowerlineCharacters { get; }; + void AddNewAxisKeyValuePair(); void DeleteAxisKeyValuePair(String key); void InitializeFontAxesVector(); @@ -103,9 +106,8 @@ namespace Microsoft.Terminal.Settings.Editor IHostedInWindow WindowRoot; static Windows.UI.Xaml.DependencyProperty AppearanceProperty { get; }; - Boolean UsingMonospaceFont { get; }; + Windows.Foundation.Collections.IObservableVector FilteredFontList { get; }; Boolean ShowAllFonts; - Boolean ShowProportionalFontWarning; IInspectable CurrentCursorShape; Boolean IsVintageCursor { get; }; @@ -121,8 +123,6 @@ namespace Microsoft.Terminal.Settings.Editor Boolean IsCustomFontWeight { get; }; Windows.Foundation.Collections.IObservableVector FontWeightList { get; }; - IInspectable CurrentFontFace { get; }; - IInspectable CurrentIntenseTextStyle; Windows.Foundation.Collections.IObservableVector IntenseTextStyleList { get; }; } diff --git a/src/cascadia/TerminalSettingsEditor/Appearances.xaml b/src/cascadia/TerminalSettingsEditor/Appearances.xaml index 35236fc509b..7d065dc4c92 100644 --- a/src/cascadia/TerminalSettingsEditor/Appearances.xaml +++ b/src/cascadia/TerminalSettingsEditor/Appearances.xaml @@ -183,7 +183,6 @@ - - - - + + IsChecked="{x:Bind ShowAllFonts, Mode=TwoWay}" /> - - + + DisplayPowerlineGlyphs(_looksLikePowerlineFont()); + _previewConnection->DisplayPowerlineGlyphs(_Profile.DefaultAppearance().HasPowerlineCharacters()); _previewControl = Control::TermControl(settings, settings, *_previewConnection); _previewControl.IsEnabled(false); _previewControl.AllowFocusWhenDisabled(false); @@ -70,25 +64,10 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation _Profile.DeleteUnfocusedAppearance(); } - bool Profiles_Appearance::_looksLikePowerlineFont() const - { - if (_Profile && _Profile.DefaultAppearance()) - { - if (const auto fontName = _Profile.DefaultAppearance().FontFace(); !fontName.empty()) - { - if (const auto font = ProfileViewModel::FindFontWithLocalizedName(fontName)) - { - return font.HasPowerlineCharacters(); - } - } - } - return false; - } - void Profiles_Appearance::_onProfilePropertyChanged(const IInspectable&, const PropertyChangedEventArgs&) const { const auto settings = _Profile.TermSettings(); - _previewConnection->DisplayPowerlineGlyphs(_looksLikePowerlineFont()); + _previewConnection->DisplayPowerlineGlyphs(_Profile.DefaultAppearance().HasPowerlineCharacters()); _previewControl.UpdateControlSettings(settings, settings); } } diff --git a/src/cascadia/TerminalSettingsEditor/Profiles_Appearance.h b/src/cascadia/TerminalSettingsEditor/Profiles_Appearance.h index ad4a779e0cc..ab9ebf66fb0 100644 --- a/src/cascadia/TerminalSettingsEditor/Profiles_Appearance.h +++ b/src/cascadia/TerminalSettingsEditor/Profiles_Appearance.h @@ -27,7 +27,6 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation private: void _onProfilePropertyChanged(const IInspectable&, const PropertyChangedEventArgs&) const; - bool _looksLikePowerlineFont() const; winrt::com_ptr _previewConnection{ nullptr }; Microsoft::Terminal::Control::TermControl _previewControl{ nullptr }; diff --git a/src/cascadia/TerminalSettingsEditor/Rendering.xaml b/src/cascadia/TerminalSettingsEditor/Rendering.xaml index fe057486859..4b9534167d0 100644 --- a/src/cascadia/TerminalSettingsEditor/Rendering.xaml +++ b/src/cascadia/TerminalSettingsEditor/Rendering.xaml @@ -15,20 +15,28 @@ + + + + - + + + - - - + - diff --git a/src/cascadia/TerminalSettingsEditor/RenderingViewModel.cpp b/src/cascadia/TerminalSettingsEditor/RenderingViewModel.cpp index 42190c4e6a7..74c4d5922a8 100644 --- a/src/cascadia/TerminalSettingsEditor/RenderingViewModel.cpp +++ b/src/cascadia/TerminalSettingsEditor/RenderingViewModel.cpp @@ -3,6 +3,9 @@ #include "pch.h" #include "RenderingViewModel.h" + +#include "EnumEntry.h" + #include "RenderingViewModel.g.cpp" using namespace winrt::Windows::Foundation; @@ -10,8 +13,9 @@ using namespace winrt::Microsoft::Terminal::Settings::Model; namespace winrt::Microsoft::Terminal::Settings::Editor::implementation { - RenderingViewModel::RenderingViewModel(Model::CascadiaSettings settings) noexcept : + RenderingViewModel::RenderingViewModel(CascadiaSettings settings) noexcept : _settings{ std::move(settings) } { + INITIALIZE_BINDABLE_ENUM_SETTING(GraphicsAPI, GraphicsAPI, winrt::Microsoft::Terminal::Control::GraphicsAPI, L"Globals_GraphicsAPI_", L"Text"); } } diff --git a/src/cascadia/TerminalSettingsEditor/RenderingViewModel.h b/src/cascadia/TerminalSettingsEditor/RenderingViewModel.h index 663d4cef877..b3042d893a1 100644 --- a/src/cascadia/TerminalSettingsEditor/RenderingViewModel.h +++ b/src/cascadia/TerminalSettingsEditor/RenderingViewModel.h @@ -4,6 +4,7 @@ #pragma once #include "RenderingViewModel.g.h" +#include "Utils.h" #include "ViewModelHelpers.h" namespace winrt::Microsoft::Terminal::Settings::Editor::implementation @@ -12,7 +13,8 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation { explicit RenderingViewModel(Model::CascadiaSettings settings) noexcept; - PERMANENT_OBSERVABLE_PROJECTED_SETTING(_settings.GlobalSettings(), ForceFullRepaintRendering); + GETSET_BINDABLE_ENUM_SETTING(GraphicsAPI, winrt::Microsoft::Terminal::Control::GraphicsAPI, _settings.GlobalSettings().GraphicsAPI); + PERMANENT_OBSERVABLE_PROJECTED_SETTING(_settings.GlobalSettings(), DisablePartialInvalidation); PERMANENT_OBSERVABLE_PROJECTED_SETTING(_settings.GlobalSettings(), SoftwareRendering); private: diff --git a/src/cascadia/TerminalSettingsEditor/RenderingViewModel.idl b/src/cascadia/TerminalSettingsEditor/RenderingViewModel.idl index aa728bfad03..1ca164fbd97 100644 --- a/src/cascadia/TerminalSettingsEditor/RenderingViewModel.idl +++ b/src/cascadia/TerminalSettingsEditor/RenderingViewModel.idl @@ -11,7 +11,9 @@ namespace Microsoft.Terminal.Settings.Editor { RenderingViewModel(Microsoft.Terminal.Settings.Model.CascadiaSettings settings); - PERMANENT_OBSERVABLE_PROJECTED_SETTING(Boolean, ForceFullRepaintRendering); + IInspectable CurrentGraphicsAPI; + Windows.Foundation.Collections.IObservableVector GraphicsAPIList { get; }; + PERMANENT_OBSERVABLE_PROJECTED_SETTING(Boolean, DisablePartialInvalidation); PERMANENT_OBSERVABLE_PROJECTED_SETTING(Boolean, SoftwareRendering); } } diff --git a/src/cascadia/TerminalSettingsEditor/Resources/de-DE/Resources.resw b/src/cascadia/TerminalSettingsEditor/Resources/de-DE/Resources.resw index 5c27fe81845..13a0809dfd4 100644 --- a/src/cascadia/TerminalSettingsEditor/Resources/de-DE/Resources.resw +++ b/src/cascadia/TerminalSettingsEditor/Resources/de-DE/Resources.resw @@ -311,13 +311,13 @@ Die Terminalanwendung, die gestartet wird, wenn eine Befehlszeilenanwendung ohne vorhandene Sitzung gestartet wird, beispielsweise vom Startmenü oder über das Dialogfeld "Ausführen". A description to clarify that the dropdown choice for default terminal will tell the operating system which Terminal application (Windows Terminal, the preview build, the legacy inbox window, or a 3rd party one) to use when starting a command line tool like CMD or Powershell that does not already have a window. - + Gesamten Bildschirm beim Anzeigen von Updates aktualisieren Header for a control to toggle the "force full repaint" setting. When enabled, the app renders new content between screen frames. - + Wenn diese Option deaktiviert ist, rendert das Terminal die Aktualisierungen nur zwischen den Frames auf den Bildschirm. - A description for what the "force full repaint" setting does. Presented near "Globals_ForceFullRepaint.Header". + A description for what the "force full repaint" setting does. Presented near "Globals_DisablePartialInvalidation.Header". Spalten @@ -446,10 +446,6 @@ An das zuletzt verwendete Fenster auf diesem Desktop anhängen An option to choose from for the "windowing behavior" setting. When selected, new instances open in the most recently used window on this virtual desktop. - - Diese Einstellungen eignen sich möglicherweise für die Problembehandlung, Sie wirken sich jedoch auf die Leistung aus. - A disclaimer presented at the top of a page. - Titelleiste ausblenden (Neustart erforderlich) Header for a control to toggle whether the title bar should be shown or not. Changing this setting requires the user to relaunch the app. diff --git a/src/cascadia/TerminalSettingsEditor/Resources/en-US/Resources.resw b/src/cascadia/TerminalSettingsEditor/Resources/en-US/Resources.resw index d134445a519..5d2615649a9 100644 --- a/src/cascadia/TerminalSettingsEditor/Resources/en-US/Resources.resw +++ b/src/cascadia/TerminalSettingsEditor/Resources/en-US/Resources.resw @@ -137,11 +137,9 @@ This color scheme is part of the built-in settings or an installed extension - To make changes to this color scheme, you must make a copy of it. - Make a copy @@ -311,13 +309,38 @@ The terminal application that launches when a command-line application is run without an existing session, like from the Start Menu or Run dialog. A description to clarify that the dropdown choice for default terminal will tell the operating system which Terminal application (Windows Terminal, the preview build, the legacy inbox window, or a 3rd party one) to use when starting a command line tool like CMD or Powershell that does not already have a window. - - Redraw entire screen when display updates - Header for a control to toggle the "force full repaint" setting. When enabled, the app renders new content between screen frames. + + Graphics API + This text is shown next to a list of choices. - - When disabled, the terminal will render only the updates to the screen between frames. - A description for what the "force full repaint" setting does. Presented near "Globals_ForceFullRepaint.Header". + + Direct3D 11 provides a more performant and feature-rich experience, whereas Direct2D is more stable. The default option "Automatic" will pick the API that best fits your graphics hardware. If you experience significant issues, consider using Direct2D. + + + Automatic + The default choice between multiple graphics APIs. + + + Direct2D + + + Direct3D 11 + + + Disable partial Swap Chain invalidation + "Swap Chain" is an official technical term by Microsoft. This text is shown next to a toggle. + + + By default, the text renderer uses a FLIP_SEQUENTIAL Swap Chain and declares dirty rectangles via the Present1 API. When this setting is enabled, a FLIP_DISCARD Swap Chain will be used instead, and no dirty rectangles will be declared. Whether one or the other is better depends on your hardware and various other factors. + {Locked="Present1","FLIP_DISCARD","FLIP_SEQUENTIAL"} + + + Use software rendering (WARP) + {Locked="WARP"} WARP is the "Windows Advanced Rasterization Platform". This text is shown next to a toggle. + + + When enabled, the terminal will use a software rasterizer (WARP). This setting should be left disabled under almost all circumstances. + {Locked="WARP"} WARP is the "Windows Advanced Rasterization Platform". Columns @@ -446,10 +469,6 @@ Attach to the most recently used window on this desktop An option to choose from for the "windowing behavior" setting. When selected, new instances open in the most recently used window on this virtual desktop. - - These settings may be useful for troubleshooting an issue, however they will impact your performance. - A disclaimer presented at the top of a page. - Hide the title bar (requires relaunch) Header for a control to toggle whether the title bar should be shown or not. Changing this setting requires the user to relaunch the app. @@ -478,14 +497,6 @@ When disabled, the window will resize smoothly. A description for what the "snap to grid on resize" setting does. Presented near "Globals_SnapToGridOnResize.Header". - - Use software rendering - Header for a control to toggle whether the terminal should use software to render content instead of the hardware. - - - When enabled, the terminal will use the software renderer (a.k.a. WARP) instead of the hardware one. - A description for what the "software rendering" setting does. Presented near "Globals_SoftwareRendering.Header". - Launch on machine startup Header for a control to toggle whether the app should launch when the user's machine starts up, or not. @@ -1873,12 +1884,12 @@ Learn more. A hyperlink displayed near Settings_PortableModeNote.Text that the user can follow for more information. - - Warning: - Title for the warning info bar used when a non monospace font face is chosen to indicate that there may be visual artifacts + + Missing fonts: + This is a label that is followed by a list of missing fonts. - - Choosing a non-monospaced font will likely result in visual artifacts. Use at your own discretion. - Warning info bar used when a non monospace font face is chosen to indicate that there may be visual artifacts + + Non-monospace fonts: + This is a label that is followed by a list of proportional fonts. - + \ No newline at end of file diff --git a/src/cascadia/TerminalSettingsEditor/Resources/es-ES/Resources.resw b/src/cascadia/TerminalSettingsEditor/Resources/es-ES/Resources.resw index ef08ff08fe1..76400095b16 100644 --- a/src/cascadia/TerminalSettingsEditor/Resources/es-ES/Resources.resw +++ b/src/cascadia/TerminalSettingsEditor/Resources/es-ES/Resources.resw @@ -311,13 +311,13 @@ Aplicación de terminal que se inicia cuando se ejecuta una aplicación de línea de comandos sin una sesión existente, como en el menú Inicio o en el cuadro de diálogo Ejecutar. A description to clarify that the dropdown choice for default terminal will tell the operating system which Terminal application (Windows Terminal, the preview build, the legacy inbox window, or a 3rd party one) to use when starting a command line tool like CMD or Powershell that does not already have a window. - + Redibujar toda la pantalla cuando se muestren actualizaciones Header for a control to toggle the "force full repaint" setting. When enabled, the app renders new content between screen frames. - + Cuando está deshabilitada, el terminal representará solo las actualizaciones de la pantalla entre fotogramas. - A description for what the "force full repaint" setting does. Presented near "Globals_ForceFullRepaint.Header". + A description for what the "force full repaint" setting does. Presented near "Globals_DisablePartialInvalidation.Header". Columnas @@ -446,10 +446,6 @@ Adjuntar a la ventana de uso más reciente en este escritorio An option to choose from for the "windowing behavior" setting. When selected, new instances open in the most recently used window on this virtual desktop. - - Esta configuración puede ser útil para solucionar un problema, pero afectará al rendimiento. - A disclaimer presented at the top of a page. - Ocultar la barra de título (requiere reiniciar) Header for a control to toggle whether the title bar should be shown or not. Changing this setting requires the user to relaunch the app. diff --git a/src/cascadia/TerminalSettingsEditor/Resources/fr-FR/Resources.resw b/src/cascadia/TerminalSettingsEditor/Resources/fr-FR/Resources.resw index 37caf840fca..2887b95e286 100644 --- a/src/cascadia/TerminalSettingsEditor/Resources/fr-FR/Resources.resw +++ b/src/cascadia/TerminalSettingsEditor/Resources/fr-FR/Resources.resw @@ -311,13 +311,13 @@ L’application Terminal qui se lance lorsqu’une application de ligne de commande est exécutée sans session existante, par exemple à partir du menu Démarrer ou de la boîte de dialogue Exécuter. A description to clarify that the dropdown choice for default terminal will tell the operating system which Terminal application (Windows Terminal, the preview build, the legacy inbox window, or a 3rd party one) to use when starting a command line tool like CMD or Powershell that does not already have a window. - + Redessiner l’intégralité de l’écran entre chaque trame Header for a control to toggle the "force full repaint" setting. When enabled, the app renders new content between screen frames. - + Une fois désactivé, le terminal n’affiche à l’écran que les modifications effectuées entre les trames. - A description for what the "force full repaint" setting does. Presented near "Globals_ForceFullRepaint.Header". + A description for what the "force full repaint" setting does. Presented near "Globals_DisablePartialInvalidation.Header". Colonnes @@ -446,10 +446,6 @@ Attacher à la dernière fenêtre utilisée sur ce bureau An option to choose from for the "windowing behavior" setting. When selected, new instances open in the most recently used window on this virtual desktop. - - Ces paramètres permettent parfois de résoudre des problèmes, mais ont un impact sur la performance. - A disclaimer presented at the top of a page. - Masquer la barre de titre (redémarrage nécessaire) Header for a control to toggle whether the title bar should be shown or not. Changing this setting requires the user to relaunch the app. diff --git a/src/cascadia/TerminalSettingsEditor/Resources/it-IT/Resources.resw b/src/cascadia/TerminalSettingsEditor/Resources/it-IT/Resources.resw index cd06973a50e..4ce446f17ba 100644 --- a/src/cascadia/TerminalSettingsEditor/Resources/it-IT/Resources.resw +++ b/src/cascadia/TerminalSettingsEditor/Resources/it-IT/Resources.resw @@ -311,13 +311,13 @@ L'applicazione terminale che viene avviata quando viene eseguita un'applicazione della riga di comando senza una sessione esistente, ad esempio dalla finestra di dialogo menu Start o Esegui. A description to clarify that the dropdown choice for default terminal will tell the operating system which Terminal application (Windows Terminal, the preview build, the legacy inbox window, or a 3rd party one) to use when starting a command line tool like CMD or Powershell that does not already have a window. - + Ridisegna l'intero schermo durante la visualizzazione degli aggiornamenti Header for a control to toggle the "force full repaint" setting. When enabled, the app renders new content between screen frames. - + Se disabilitato, il terminale eseguirà il rendering solo degli aggiornamenti sullo schermo tra i fotogrammi. - A description for what the "force full repaint" setting does. Presented near "Globals_ForceFullRepaint.Header". + A description for what the "force full repaint" setting does. Presented near "Globals_DisablePartialInvalidation.Header". Colonne @@ -446,10 +446,6 @@ Allega alla finestra usata più di recente su questo desktop An option to choose from for the "windowing behavior" setting. When selected, new instances open in the most recently used window on this virtual desktop. - - Queste impostazioni potrebbero essere utili per la risoluzione di un problema, ma influiranno sulle prestazioni. - A disclaimer presented at the top of a page. - Nascondi la barra del titolo (sarà necessario riavviare) Header for a control to toggle whether the title bar should be shown or not. Changing this setting requires the user to relaunch the app. diff --git a/src/cascadia/TerminalSettingsEditor/Resources/ja-JP/Resources.resw b/src/cascadia/TerminalSettingsEditor/Resources/ja-JP/Resources.resw index a8935f14470..d57a8288d57 100644 --- a/src/cascadia/TerminalSettingsEditor/Resources/ja-JP/Resources.resw +++ b/src/cascadia/TerminalSettingsEditor/Resources/ja-JP/Resources.resw @@ -311,13 +311,13 @@ [スタート] メニューや [ファイル名を指定して実行] ダイアログなど、既存のセッションなしでコマンドライン アプリケーションを実行したときに起動するターミナル アプリケーション。 A description to clarify that the dropdown choice for default terminal will tell the operating system which Terminal application (Windows Terminal, the preview build, the legacy inbox window, or a 3rd party one) to use when starting a command line tool like CMD or Powershell that does not already have a window. - + ディスプレイの更新時に画面全体を再描画する Header for a control to toggle the "force full repaint" setting. When enabled, the app renders new content between screen frames. - + 無効にすると、ターミナルはフレームとフレームの間において情報更新分のみレンダリングします。 - A description for what the "force full repaint" setting does. Presented near "Globals_ForceFullRepaint.Header". + A description for what the "force full repaint" setting does. Presented near "Globals_DisablePartialInvalidation.Header". @@ -446,10 +446,6 @@ このデスクトップで最近使用したウィンドウに接続する An option to choose from for the "windowing behavior" setting. When selected, new instances open in the most recently used window on this virtual desktop. - - これらの設定は問題のトラブルシューティングに役立つ場合がありますが、パフォーマンスに影響を与えます。 - A disclaimer presented at the top of a page. - タイトル バーを非表示にする (再起動が必要) Header for a control to toggle whether the title bar should be shown or not. Changing this setting requires the user to relaunch the app. diff --git a/src/cascadia/TerminalSettingsEditor/Resources/ko-KR/Resources.resw b/src/cascadia/TerminalSettingsEditor/Resources/ko-KR/Resources.resw index 9c8f8e9663f..aef454bd85d 100644 --- a/src/cascadia/TerminalSettingsEditor/Resources/ko-KR/Resources.resw +++ b/src/cascadia/TerminalSettingsEditor/Resources/ko-KR/Resources.resw @@ -311,13 +311,13 @@ 시작 메뉴나 실행 대화 상자와 같이 기존 세션이 없으면 명령 줄 응용 프로그램을 실행하고 있는 터미널 응용 프로그램입니다. A description to clarify that the dropdown choice for default terminal will tell the operating system which Terminal application (Windows Terminal, the preview build, the legacy inbox window, or a 3rd party one) to use when starting a command line tool like CMD or Powershell that does not already have a window. - + 업데이트를 표시할 때 전체 화면 다시 그리기 Header for a control to toggle the "force full repaint" setting. When enabled, the app renders new content between screen frames. - + 사용하지 않도록 설정하면 터미널이 프레임 간 화면 업데이트만 렌더링합니다. - A description for what the "force full repaint" setting does. Presented near "Globals_ForceFullRepaint.Header". + A description for what the "force full repaint" setting does. Presented near "Globals_DisablePartialInvalidation.Header". @@ -446,10 +446,6 @@ 이 데스크톱에서 가장 최근에 사용한 창에 첨부 An option to choose from for the "windowing behavior" setting. When selected, new instances open in the most recently used window on this virtual desktop. - - 이런 설정은 문제를 해결하는 데는 유용할 수 있지만 성능에 영향을 미칩니다. - A disclaimer presented at the top of a page. - 제목 표시줄 숨기기(다시 시작해야 함) Header for a control to toggle whether the title bar should be shown or not. Changing this setting requires the user to relaunch the app. diff --git a/src/cascadia/TerminalSettingsEditor/Resources/pt-BR/Resources.resw b/src/cascadia/TerminalSettingsEditor/Resources/pt-BR/Resources.resw index 543cb2ec43d..56fff0a611d 100644 --- a/src/cascadia/TerminalSettingsEditor/Resources/pt-BR/Resources.resw +++ b/src/cascadia/TerminalSettingsEditor/Resources/pt-BR/Resources.resw @@ -311,13 +311,13 @@ O aplicativo de terminal que é iniciado quando um aplicativo de linha de comando é executado sem uma sessão existente, como no menu iniciar ou na caixa de diálogo Executar. A description to clarify that the dropdown choice for default terminal will tell the operating system which Terminal application (Windows Terminal, the preview build, the legacy inbox window, or a 3rd party one) to use when starting a command line tool like CMD or Powershell that does not already have a window. - + Redesenhar a tela inteira ao exibir atualizações Header for a control to toggle the "force full repaint" setting. When enabled, the app renders new content between screen frames. - + Quando desabilitado, o terminal renderizará somente as atualizações na tela entre quadros. - A description for what the "force full repaint" setting does. Presented near "Globals_ForceFullRepaint.Header". + A description for what the "force full repaint" setting does. Presented near "Globals_DisablePartialInvalidation.Header". Colunas @@ -446,10 +446,6 @@ Anexar à última janela usada nesta área de trabalho An option to choose from for the "windowing behavior" setting. When selected, new instances open in the most recently used window on this virtual desktop. - - Essas configurações podem ser úteis para solucionar um problema, no entanto, elas impactarão seu desempenho. - A disclaimer presented at the top of a page. - Oculta a barra do título (requer reinicialização) Header for a control to toggle whether the title bar should be shown or not. Changing this setting requires the user to relaunch the app. diff --git a/src/cascadia/TerminalSettingsEditor/Resources/qps-ploc/Resources.resw b/src/cascadia/TerminalSettingsEditor/Resources/qps-ploc/Resources.resw index f7193ce8d63..f954fb623fb 100644 --- a/src/cascadia/TerminalSettingsEditor/Resources/qps-ploc/Resources.resw +++ b/src/cascadia/TerminalSettingsEditor/Resources/qps-ploc/Resources.resw @@ -311,13 +311,13 @@ Ťђě ţэřмīηãĺ ǻφφℓï¢ǻтΐбň ŧђáт łáůйčĥēś ẅĥэп ā сöмmǻńđ-ľіʼnэ ăρρĺįċāτΐôⁿ ϊš яųñ шíţћöυţ ªņ ĕхϊŝŧĭñğ şěŝѕïŏπ, ŀĩĸė ƒѓом ţћĕ Ŝτáґт Μęπü бг Яųņ ďīäℓöğ. !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! ! A description to clarify that the dropdown choice for default terminal will tell the operating system which Terminal application (Windows Terminal, the preview build, the legacy inbox window, or a 3rd party one) to use when starting a command line tool like CMD or Powershell that does not already have a window. - + Ŗёδѓªŵ еñţιŗę šĉřέëŋ шн℮й đĩşρℓâў υρďάţëš !!! !!! !!! !!! Header for a control to toggle the "force full repaint" setting. When enabled, the app renders new content between screen frames. - + Щħ℮п đįѕāъĺèđ, ťђέ тèřмΐņäľ ωìľľ řεʼnďęŗ όʼnľŷ ŧђέ ŭρďăţзŝ τö ŧђë šсгèзņ вêţшзэʼn ƒяāméş. !!! !!! !!! !!! !!! !!! !!! !!! ! - A description for what the "force full repaint" setting does. Presented near "Globals_ForceFullRepaint.Header". + A description for what the "force full repaint" setting does. Presented near "Globals_DisablePartialInvalidation.Header". Ċσŀùмñѕ !! @@ -446,10 +446,6 @@ Äţŧαĉђ τǿ τħе mοѕт ѓěςęńτļγ ûѕєď ŵíñðοω øи тћϊѕ ďёšкτǿφ !!! !!! !!! !!! !!! ! An option to choose from for the "windowing behavior" setting. When selected, new instances open in the most recently used window on this virtual desktop. - - Τћέŝė ѕëτтίņģš мâу ьê üѕēƒµľ ƒόґ τřóųьŀėѕĥбοτілġ ǻл іŝśü℮, нσώéνєŕ τĥêў ŵīŀł ΐmρåςť убŭя ρěгƒόґмåñĉę. !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! - A disclaimer presented at the top of a page. - Ĥìđε тħê τīţĺё ъªř (ŗėqūΐŗêś яеľаϋʼnčћ) !!! !!! !!! !! Header for a control to toggle whether the title bar should be shown or not. Changing this setting requires the user to relaunch the app. diff --git a/src/cascadia/TerminalSettingsEditor/Resources/qps-ploca/Resources.resw b/src/cascadia/TerminalSettingsEditor/Resources/qps-ploca/Resources.resw index f7193ce8d63..f954fb623fb 100644 --- a/src/cascadia/TerminalSettingsEditor/Resources/qps-ploca/Resources.resw +++ b/src/cascadia/TerminalSettingsEditor/Resources/qps-ploca/Resources.resw @@ -311,13 +311,13 @@ Ťђě ţэřмīηãĺ ǻφφℓï¢ǻтΐбň ŧђáт łáůйčĥēś ẅĥэп ā сöмmǻńđ-ľіʼnэ ăρρĺįċāτΐôⁿ ϊš яųñ шíţћöυţ ªņ ĕхϊŝŧĭñğ şěŝѕïŏπ, ŀĩĸė ƒѓом ţћĕ Ŝτáґт Μęπü бг Яųņ ďīäℓöğ. !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! ! A description to clarify that the dropdown choice for default terminal will tell the operating system which Terminal application (Windows Terminal, the preview build, the legacy inbox window, or a 3rd party one) to use when starting a command line tool like CMD or Powershell that does not already have a window. - + Ŗёδѓªŵ еñţιŗę šĉřέëŋ шн℮й đĩşρℓâў υρďάţëš !!! !!! !!! !!! Header for a control to toggle the "force full repaint" setting. When enabled, the app renders new content between screen frames. - + Щħ℮п đįѕāъĺèđ, ťђέ тèřмΐņäľ ωìľľ řεʼnďęŗ όʼnľŷ ŧђέ ŭρďăţзŝ τö ŧђë šсгèзņ вêţшзэʼn ƒяāméş. !!! !!! !!! !!! !!! !!! !!! !!! ! - A description for what the "force full repaint" setting does. Presented near "Globals_ForceFullRepaint.Header". + A description for what the "force full repaint" setting does. Presented near "Globals_DisablePartialInvalidation.Header". Ċσŀùмñѕ !! @@ -446,10 +446,6 @@ Äţŧαĉђ τǿ τħе mοѕт ѓěςęńτļγ ûѕєď ŵíñðοω øи тћϊѕ ďёšкτǿφ !!! !!! !!! !!! !!! ! An option to choose from for the "windowing behavior" setting. When selected, new instances open in the most recently used window on this virtual desktop. - - Τћέŝė ѕëτтίņģš мâу ьê üѕēƒµľ ƒόґ τřóųьŀėѕĥбοτілġ ǻл іŝśü℮, нσώéνєŕ τĥêў ŵīŀł ΐmρåςť убŭя ρěгƒόґмåñĉę. !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! - A disclaimer presented at the top of a page. - Ĥìđε тħê τīţĺё ъªř (ŗėqūΐŗêś яеľаϋʼnčћ) !!! !!! !!! !! Header for a control to toggle whether the title bar should be shown or not. Changing this setting requires the user to relaunch the app. diff --git a/src/cascadia/TerminalSettingsEditor/Resources/qps-plocm/Resources.resw b/src/cascadia/TerminalSettingsEditor/Resources/qps-plocm/Resources.resw index f7193ce8d63..f954fb623fb 100644 --- a/src/cascadia/TerminalSettingsEditor/Resources/qps-plocm/Resources.resw +++ b/src/cascadia/TerminalSettingsEditor/Resources/qps-plocm/Resources.resw @@ -311,13 +311,13 @@ Ťђě ţэřмīηãĺ ǻφφℓï¢ǻтΐбň ŧђáт łáůйčĥēś ẅĥэп ā сöмmǻńđ-ľіʼnэ ăρρĺįċāτΐôⁿ ϊš яųñ шíţћöυţ ªņ ĕхϊŝŧĭñğ şěŝѕïŏπ, ŀĩĸė ƒѓом ţћĕ Ŝτáґт Μęπü бг Яųņ ďīäℓöğ. !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! ! A description to clarify that the dropdown choice for default terminal will tell the operating system which Terminal application (Windows Terminal, the preview build, the legacy inbox window, or a 3rd party one) to use when starting a command line tool like CMD or Powershell that does not already have a window. - + Ŗёδѓªŵ еñţιŗę šĉřέëŋ шн℮й đĩşρℓâў υρďάţëš !!! !!! !!! !!! Header for a control to toggle the "force full repaint" setting. When enabled, the app renders new content between screen frames. - + Щħ℮п đįѕāъĺèđ, ťђέ тèřмΐņäľ ωìľľ řεʼnďęŗ όʼnľŷ ŧђέ ŭρďăţзŝ τö ŧђë šсгèзņ вêţшзэʼn ƒяāméş. !!! !!! !!! !!! !!! !!! !!! !!! ! - A description for what the "force full repaint" setting does. Presented near "Globals_ForceFullRepaint.Header". + A description for what the "force full repaint" setting does. Presented near "Globals_DisablePartialInvalidation.Header". Ċσŀùмñѕ !! @@ -446,10 +446,6 @@ Äţŧαĉђ τǿ τħе mοѕт ѓěςęńτļγ ûѕєď ŵíñðοω øи тћϊѕ ďёšкτǿφ !!! !!! !!! !!! !!! ! An option to choose from for the "windowing behavior" setting. When selected, new instances open in the most recently used window on this virtual desktop. - - Τћέŝė ѕëτтίņģš мâу ьê üѕēƒµľ ƒόґ τřóųьŀėѕĥбοτілġ ǻл іŝśü℮, нσώéνєŕ τĥêў ŵīŀł ΐmρåςť убŭя ρěгƒόґмåñĉę. !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! - A disclaimer presented at the top of a page. - Ĥìđε тħê τīţĺё ъªř (ŗėqūΐŗêś яеľаϋʼnčћ) !!! !!! !!! !! Header for a control to toggle whether the title bar should be shown or not. Changing this setting requires the user to relaunch the app. diff --git a/src/cascadia/TerminalSettingsEditor/Resources/ru-RU/Resources.resw b/src/cascadia/TerminalSettingsEditor/Resources/ru-RU/Resources.resw index 3af0c8f8f6b..c935675a936 100644 --- a/src/cascadia/TerminalSettingsEditor/Resources/ru-RU/Resources.resw +++ b/src/cascadia/TerminalSettingsEditor/Resources/ru-RU/Resources.resw @@ -311,13 +311,13 @@ Приложение терминала, запускаемое при запуске приложения командной строки без существующего сеанса, например из меню "Пуск" или из диалогового окна "Выполнить". A description to clarify that the dropdown choice for default terminal will tell the operating system which Terminal application (Windows Terminal, the preview build, the legacy inbox window, or a 3rd party one) to use when starting a command line tool like CMD or Powershell that does not already have a window. - + Перерисовка всего экрана при обновлении дисплея Header for a control to toggle the "force full repaint" setting. When enabled, the app renders new content between screen frames. - + Если этот параметр отключен, терминал будет отображать только обновления экрана между кадрами. - A description for what the "force full repaint" setting does. Presented near "Globals_ForceFullRepaint.Header". + A description for what the "force full repaint" setting does. Presented near "Globals_DisablePartialInvalidation.Header". Столбцы @@ -446,10 +446,6 @@ Присоединять к последнему использованному окну на этом рабочем столе An option to choose from for the "windowing behavior" setting. When selected, new instances open in the most recently used window on this virtual desktop. - - Эти параметры могут быть полезны для устранения проблемы, но они повлияют на вашу производительность. - A disclaimer presented at the top of a page. - Скрыть заголовок окна (требуется перезапуск) Header for a control to toggle whether the title bar should be shown or not. Changing this setting requires the user to relaunch the app. diff --git a/src/cascadia/TerminalSettingsEditor/Resources/zh-CN/Resources.resw b/src/cascadia/TerminalSettingsEditor/Resources/zh-CN/Resources.resw index 0d602e9adc0..44f22000ddb 100644 --- a/src/cascadia/TerminalSettingsEditor/Resources/zh-CN/Resources.resw +++ b/src/cascadia/TerminalSettingsEditor/Resources/zh-CN/Resources.resw @@ -311,13 +311,13 @@ 当命令行应用程序在没有现有会话(例如从“开始菜单”或“运行”对话框)运行时启动的终端应用程序。 A description to clarify that the dropdown choice for default terminal will tell the operating system which Terminal application (Windows Terminal, the preview build, the legacy inbox window, or a 3rd party one) to use when starting a command line tool like CMD or Powershell that does not already have a window. - + 显示内容更新时重绘整个屏幕 Header for a control to toggle the "force full repaint" setting. When enabled, the app renders new content between screen frames. - + 禁用后,终端将仅在帧之间将更新呈现到屏幕。 - A description for what the "force full repaint" setting does. Presented near "Globals_ForceFullRepaint.Header". + A description for what the "force full repaint" setting does. Presented near "Globals_DisablePartialInvalidation.Header". @@ -446,10 +446,6 @@ 附加到此桌面上最近使用的窗口 An option to choose from for the "windowing behavior" setting. When selected, new instances open in the most recently used window on this virtual desktop. - - 这些设置可能有助于解决问题,但会对性能产生影响。 - A disclaimer presented at the top of a page. - 隐藏标题栏(需要重新启动) Header for a control to toggle whether the title bar should be shown or not. Changing this setting requires the user to relaunch the app. diff --git a/src/cascadia/TerminalSettingsEditor/Resources/zh-TW/Resources.resw b/src/cascadia/TerminalSettingsEditor/Resources/zh-TW/Resources.resw index 1b1ffb8bbf2..13ab841670c 100644 --- a/src/cascadia/TerminalSettingsEditor/Resources/zh-TW/Resources.resw +++ b/src/cascadia/TerminalSettingsEditor/Resources/zh-TW/Resources.resw @@ -311,13 +311,13 @@ 當執行命令列應用程式且不使用現有工作模式而啟動的終端機應用程式 (例如從 [開始] 功能表或 [執行] 對話方塊)。 A description to clarify that the dropdown choice for default terminal will tell the operating system which Terminal application (Windows Terminal, the preview build, the legacy inbox window, or a 3rd party one) to use when starting a command line tool like CMD or Powershell that does not already have a window. - + 顯示更新時,重新繪製整個螢幕 Header for a control to toggle the "force full repaint" setting. When enabled, the app renders new content between screen frames. - + 停用時,終端機只會轉譯框架間螢幕的更新。 - A description for what the "force full repaint" setting does. Presented near "Globals_ForceFullRepaint.Header". + A description for what the "force full repaint" setting does. Presented near "Globals_DisablePartialInvalidation.Header". @@ -446,10 +446,6 @@ 附加到此桌面最近使用的視窗 An option to choose from for the "windowing behavior" setting. When selected, new instances open in the most recently used window on this virtual desktop. - - 這些設定可能有助於解決問題,但會影響效能。 - A disclaimer presented at the top of a page. - 隱藏標題列 (需要重新啟動) Header for a control to toggle whether the title bar should be shown or not. Changing this setting requires the user to relaunch the app. diff --git a/src/cascadia/TerminalSettingsEditor/ViewModelHelpers.h b/src/cascadia/TerminalSettingsEditor/ViewModelHelpers.h index e306233ab63..61597935390 100644 --- a/src/cascadia/TerminalSettingsEditor/ViewModelHelpers.h +++ b/src/cascadia/TerminalSettingsEditor/ViewModelHelpers.h @@ -39,20 +39,21 @@ struct ViewModelHelper #define _BASE_OBSERVABLE_PROJECTED_SETTING(target, name) \ public: \ - auto name() const noexcept \ + auto name() const \ { \ return target.name(); \ }; \ template \ void name(const T& value) \ { \ - if (name() != value) \ + const auto t = target; \ + if (t.name() != value) \ { \ - target.name(value); \ + t.name(value); \ _NotifyChanges(L"Has" #name, L## #name); \ } \ } \ - bool Has##name() \ + bool Has##name() const \ { \ return target.Has##name(); \ } @@ -63,14 +64,15 @@ public: \ _BASE_OBSERVABLE_PROJECTED_SETTING(target, name) \ void Clear##name() \ { \ - const auto hadValue{ target.Has##name() }; \ - target.Clear##name(); \ + const auto t = target; \ + const auto hadValue{ t.Has##name() }; \ + t.Clear##name(); \ if (hadValue) \ { \ _NotifyChanges(L"Has" #name, L## #name); \ } \ } \ - auto name##OverrideSource() \ + auto name##OverrideSource() const \ { \ return target.name##OverrideSource(); \ } diff --git a/src/cascadia/TerminalSettingsModel/ActionAndArgs.cpp b/src/cascadia/TerminalSettingsModel/ActionAndArgs.cpp index 50e795c77c4..a8432f3e55c 100644 --- a/src/cascadia/TerminalSettingsModel/ActionAndArgs.cpp +++ b/src/cascadia/TerminalSettingsModel/ActionAndArgs.cpp @@ -97,6 +97,7 @@ static constexpr std::string_view ShowContextMenuKey{ "showContextMenu" }; static constexpr std::string_view ExpandSelectionToWordKey{ "expandSelectionToWord" }; static constexpr std::string_view RestartConnectionKey{ "restartConnection" }; static constexpr std::string_view ToggleBroadcastInputKey{ "toggleBroadcastInput" }; +static constexpr std::string_view OpenScratchpadKey{ "experimental.openScratchpad" }; static constexpr std::string_view OpenAboutKey{ "openAbout" }; static constexpr std::string_view ActionKey{ "action" }; @@ -433,6 +434,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation { ShortcutAction::ExpandSelectionToWord, RS_(L"ExpandSelectionToWordCommandKey") }, { ShortcutAction::RestartConnection, RS_(L"RestartConnectionKey") }, { ShortcutAction::ToggleBroadcastInput, RS_(L"ToggleBroadcastInputCommandKey") }, + { ShortcutAction::OpenScratchpad, RS_(L"OpenScratchpadKey") }, { ShortcutAction::OpenAbout, RS_(L"OpenAboutCommandKey") }, }; }(); diff --git a/src/cascadia/TerminalSettingsModel/AllShortcutActions.h b/src/cascadia/TerminalSettingsModel/AllShortcutActions.h index d34854a21b4..0652a06b132 100644 --- a/src/cascadia/TerminalSettingsModel/AllShortcutActions.h +++ b/src/cascadia/TerminalSettingsModel/AllShortcutActions.h @@ -111,6 +111,7 @@ ON_ALL_ACTIONS(ToggleAIChat) \ ON_ALL_ACTIONS(RestartConnection) \ ON_ALL_ACTIONS(ToggleBroadcastInput) \ + ON_ALL_ACTIONS(OpenScratchpad) \ ON_ALL_ACTIONS(OpenAbout) #define ALL_SHORTCUT_ACTIONS_WITH_ARGS \ diff --git a/src/cascadia/TerminalSettingsModel/EnumMappings.cpp b/src/cascadia/TerminalSettingsModel/EnumMappings.cpp index 8b3d936a4a3..15665908b78 100644 --- a/src/cascadia/TerminalSettingsModel/EnumMappings.cpp +++ b/src/cascadia/TerminalSettingsModel/EnumMappings.cpp @@ -39,6 +39,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation DEFINE_ENUM_MAP(Microsoft::Terminal::Control::CopyFormat, CopyFormat); DEFINE_ENUM_MAP(Model::WindowingMode, WindowingMode); DEFINE_ENUM_MAP(Microsoft::Terminal::Core::MatchMode, MatchMode); + DEFINE_ENUM_MAP(Microsoft::Terminal::Control::GraphicsAPI, GraphicsAPI); // Profile Settings DEFINE_ENUM_MAP(Model::CloseOnExitMode, CloseOnExitMode); diff --git a/src/cascadia/TerminalSettingsModel/EnumMappings.h b/src/cascadia/TerminalSettingsModel/EnumMappings.h index dfdd6c46502..722ce920953 100644 --- a/src/cascadia/TerminalSettingsModel/EnumMappings.h +++ b/src/cascadia/TerminalSettingsModel/EnumMappings.h @@ -35,6 +35,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation static winrt::Windows::Foundation::Collections::IMap CopyFormat(); static winrt::Windows::Foundation::Collections::IMap WindowingMode(); static winrt::Windows::Foundation::Collections::IMap MatchMode(); + static winrt::Windows::Foundation::Collections::IMap GraphicsAPI(); // Profile Settings static winrt::Windows::Foundation::Collections::IMap CloseOnExitMode(); diff --git a/src/cascadia/TerminalSettingsModel/EnumMappings.idl b/src/cascadia/TerminalSettingsModel/EnumMappings.idl index a74dc96b483..11801182999 100644 --- a/src/cascadia/TerminalSettingsModel/EnumMappings.idl +++ b/src/cascadia/TerminalSettingsModel/EnumMappings.idl @@ -17,6 +17,7 @@ namespace Microsoft.Terminal.Settings.Model static Windows.Foundation.Collections.IMap CopyFormat { get; }; static Windows.Foundation.Collections.IMap WindowingMode { get; }; static Windows.Foundation.Collections.IMap MatchMode { get; }; + static Windows.Foundation.Collections.IMap GraphicsAPI { get; }; // Profile Settings static Windows.Foundation.Collections.IMap CloseOnExitMode { get; }; diff --git a/src/cascadia/TerminalSettingsModel/GlobalAppSettings.cpp b/src/cascadia/TerminalSettingsModel/GlobalAppSettings.cpp index 7238d38c0cd..c307050a998 100644 --- a/src/cascadia/TerminalSettingsModel/GlobalAppSettings.cpp +++ b/src/cascadia/TerminalSettingsModel/GlobalAppSettings.cpp @@ -220,8 +220,24 @@ const std::vector // Return Value: // - the JsonObject representing this instance -Json::Value GlobalAppSettings::ToJson() const +Json::Value GlobalAppSettings::ToJson() { + // These experimental options should be removed from the settings file if they're at their default value. + // This prevents them from sticking around forever, even if the user was just experimenting with them. + // One could consider this a workaround for the settings UI right now not having a "reset to default" button for these. + if (_GraphicsAPI == Control::GraphicsAPI::Automatic) + { + _GraphicsAPI.reset(); + } + if (_DisablePartialInvalidation == false) + { + _DisablePartialInvalidation.reset(); + } + if (_SoftwareRendering == false) + { + _SoftwareRendering.reset(); + } + Json::Value json{ Json::ValueType::objectValue }; JsonUtils::SetValueForKey(json, DefaultProfileKey, _UnparsedDefaultProfile); diff --git a/src/cascadia/TerminalSettingsModel/GlobalAppSettings.h b/src/cascadia/TerminalSettingsModel/GlobalAppSettings.h index aa7d8c0147f..c977094df53 100644 --- a/src/cascadia/TerminalSettingsModel/GlobalAppSettings.h +++ b/src/cascadia/TerminalSettingsModel/GlobalAppSettings.h @@ -52,7 +52,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation void LayerJson(const Json::Value& json, const OriginTag origin); void LayerActionsFrom(const Json::Value& json, const OriginTag origin, const bool withKeybindings = true); - Json::Value ToJson() const; + Json::Value ToJson(); const std::vector& KeybindingsWarnings() const; diff --git a/src/cascadia/TerminalSettingsModel/GlobalAppSettings.idl b/src/cascadia/TerminalSettingsModel/GlobalAppSettings.idl index 612262d4b11..7e46bcc0517 100644 --- a/src/cascadia/TerminalSettingsModel/GlobalAppSettings.idl +++ b/src/cascadia/TerminalSettingsModel/GlobalAppSettings.idl @@ -76,7 +76,8 @@ namespace Microsoft.Terminal.Settings.Model INHERITABLE_SETTING(FirstWindowPreference, FirstWindowPreference); INHERITABLE_SETTING(LaunchMode, LaunchMode); INHERITABLE_SETTING(Boolean, SnapToGridOnResize); - INHERITABLE_SETTING(Boolean, ForceFullRepaintRendering); + INHERITABLE_SETTING(Microsoft.Terminal.Control.GraphicsAPI, GraphicsAPI); + INHERITABLE_SETTING(Boolean, DisablePartialInvalidation); INHERITABLE_SETTING(Boolean, SoftwareRendering); INHERITABLE_SETTING(Boolean, UseBackgroundImageForWindow); INHERITABLE_SETTING(Boolean, ForceVTInput); diff --git a/src/cascadia/TerminalSettingsModel/MTSMSettings.h b/src/cascadia/TerminalSettingsModel/MTSMSettings.h index cb8eb8bcdff..b4c678fd997 100644 --- a/src/cascadia/TerminalSettingsModel/MTSMSettings.h +++ b/src/cascadia/TerminalSettingsModel/MTSMSettings.h @@ -24,8 +24,9 @@ Author(s): X(hstring, WordDelimiters, "wordDelimiters", DEFAULT_WORD_DELIMITERS) \ X(bool, CopyOnSelect, "copyOnSelect", false) \ X(bool, FocusFollowMouse, "focusFollowMouse", false) \ - X(bool, ForceFullRepaintRendering, "experimental.rendering.forceFullRepaint", false) \ - X(bool, SoftwareRendering, "experimental.rendering.software", false) \ + X(winrt::Microsoft::Terminal::Control::GraphicsAPI, GraphicsAPI, "rendering.graphicsAPI") \ + X(bool, DisablePartialInvalidation, "rendering.disablePartialInvalidation", false) \ + X(bool, SoftwareRendering, "rendering.software", false) \ X(bool, UseBackgroundImageForWindow, "experimental.useBackgroundImageForWindow", false) \ X(bool, ForceVTInput, "experimental.input.forceVT", false) \ X(bool, TrimBlockSelection, "trimBlockSelection", true) \ diff --git a/src/cascadia/TerminalSettingsModel/Resources/en-US/Resources.resw b/src/cascadia/TerminalSettingsModel/Resources/en-US/Resources.resw index 93f2296cec1..91e943e9c21 100644 --- a/src/cascadia/TerminalSettingsModel/Resources/en-US/Resources.resw +++ b/src/cascadia/TerminalSettingsModel/Resources/en-US/Resources.resw @@ -703,6 +703,9 @@ Restart connection + + Open scratchpad + Select next command output @@ -727,4 +730,4 @@ Open about dialog This will open the "about" dialog, to display version info and other documentation - + \ No newline at end of file diff --git a/src/cascadia/TerminalSettingsModel/TerminalSettings.cpp b/src/cascadia/TerminalSettingsModel/TerminalSettings.cpp index e5c07a71103..682d79e19c7 100644 --- a/src/cascadia/TerminalSettingsModel/TerminalSettings.cpp +++ b/src/cascadia/TerminalSettingsModel/TerminalSettings.cpp @@ -360,7 +360,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation _CopyOnSelect = globalSettings.CopyOnSelect(); _CopyFormatting = globalSettings.CopyFormatting(); _FocusFollowMouse = globalSettings.FocusFollowMouse(); - _ForceFullRepaintRendering = globalSettings.ForceFullRepaintRendering(); + _GraphicsAPI = globalSettings.GraphicsAPI(); + _DisablePartialInvalidation = globalSettings.DisablePartialInvalidation(); _SoftwareRendering = globalSettings.SoftwareRendering(); _UseBackgroundImageForWindow = globalSettings.UseBackgroundImageForWindow(); _ForceVTInput = globalSettings.ForceVTInput(); diff --git a/src/cascadia/TerminalSettingsModel/TerminalSettings.h b/src/cascadia/TerminalSettingsModel/TerminalSettings.h index 184e05ac8f9..99e3b2d120e 100644 --- a/src/cascadia/TerminalSettingsModel/TerminalSettings.h +++ b/src/cascadia/TerminalSettingsModel/TerminalSettings.h @@ -155,7 +155,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation INHERITABLE_SETTING(Model::TerminalSettings, Microsoft::Terminal::Control::TextAntialiasingMode, AntialiasingMode, Microsoft::Terminal::Control::TextAntialiasingMode::Grayscale); INHERITABLE_SETTING(Model::TerminalSettings, bool, RetroTerminalEffect, false); - INHERITABLE_SETTING(Model::TerminalSettings, bool, ForceFullRepaintRendering, false); + INHERITABLE_SETTING(Model::TerminalSettings, Microsoft::Terminal::Control::GraphicsAPI, GraphicsAPI); + INHERITABLE_SETTING(Model::TerminalSettings, bool, DisablePartialInvalidation, false); INHERITABLE_SETTING(Model::TerminalSettings, bool, SoftwareRendering, false); INHERITABLE_SETTING(Model::TerminalSettings, bool, UseBackgroundImageForWindow, false); INHERITABLE_SETTING(Model::TerminalSettings, bool, ForceVTInput, false); diff --git a/src/cascadia/TerminalSettingsModel/TerminalSettingsSerializationHelpers.h b/src/cascadia/TerminalSettingsModel/TerminalSettingsSerializationHelpers.h index f1f2bc11048..1168e0660fb 100644 --- a/src/cascadia/TerminalSettingsModel/TerminalSettingsSerializationHelpers.h +++ b/src/cascadia/TerminalSettingsModel/TerminalSettingsSerializationHelpers.h @@ -761,3 +761,12 @@ struct ::Microsoft::Terminal::Settings::Model::JsonUtils::ConversionTrait<::winr return "SelectionColor (#rrggbb, #rgb, #rrggbbaa, iNN)"; } }; + +JSON_ENUM_MAPPER(::winrt::Microsoft::Terminal::Control::GraphicsAPI) +{ + JSON_MAPPINGS(3) = { + pair_type{ "automatic", ValueType::Automatic }, + pair_type{ "direct2d", ValueType::Direct2D }, + pair_type{ "direct3d11", ValueType::Direct3D11 }, + }; +}; diff --git a/src/cascadia/UIHelpers/Converters.cpp b/src/cascadia/UIHelpers/Converters.cpp index 997f0e5ec7d..ce5123995d4 100644 --- a/src/cascadia/UIHelpers/Converters.cpp +++ b/src/cascadia/UIHelpers/Converters.cpp @@ -40,6 +40,11 @@ namespace winrt::Microsoft::Terminal::UI::implementation return expected != actual; } + bool Converters::StringNotEmpty(const winrt::hstring& value) + { + return !value.empty(); + } + winrt::Windows::UI::Xaml::Visibility Converters::StringNotEmptyToVisibility(const winrt::hstring& value) { return value.empty() ? winrt::Windows::UI::Xaml::Visibility::Collapsed : winrt::Windows::UI::Xaml::Visibility::Visible; diff --git a/src/cascadia/UIHelpers/Converters.h b/src/cascadia/UIHelpers/Converters.h index 6fda669605b..af8bcecb458 100644 --- a/src/cascadia/UIHelpers/Converters.h +++ b/src/cascadia/UIHelpers/Converters.h @@ -20,6 +20,7 @@ namespace winrt::Microsoft::Terminal::UI::implementation // Strings static bool StringsAreNotEqual(const winrt::hstring& expected, const winrt::hstring& actual); + static bool StringNotEmpty(const winrt::hstring& value); static winrt::Windows::UI::Xaml::Visibility StringNotEmptyToVisibility(const winrt::hstring& value); static winrt::hstring StringOrEmptyIfPlaceholder(const winrt::hstring& placeholder, const winrt::hstring& value); diff --git a/src/cascadia/UIHelpers/Converters.idl b/src/cascadia/UIHelpers/Converters.idl index 2c9ef90e39d..baf8a97af65 100644 --- a/src/cascadia/UIHelpers/Converters.idl +++ b/src/cascadia/UIHelpers/Converters.idl @@ -18,6 +18,7 @@ namespace Microsoft.Terminal.UI // Strings static Boolean StringsAreNotEqual(String expected, String actual); + static Boolean StringNotEmpty(String value); static Windows.UI.Xaml.Visibility StringNotEmptyToVisibility(String value); static String StringOrEmptyIfPlaceholder(String placeholder, String value); diff --git a/src/cascadia/UnitTests_SettingsModel/SerializationTests.cpp b/src/cascadia/UnitTests_SettingsModel/SerializationTests.cpp index 455c0b78c27..8cfd4a14ccf 100644 --- a/src/cascadia/UnitTests_SettingsModel/SerializationTests.cpp +++ b/src/cascadia/UnitTests_SettingsModel/SerializationTests.cpp @@ -107,8 +107,6 @@ namespace SettingsModelUnitTests "trimPaste": true, "experimental.input.forceVT": false, - "experimental.rendering.forceFullRepaint": false, - "experimental.rendering.software": false, "actions": [] })" }; diff --git a/src/cascadia/inc/ControlProperties.h b/src/cascadia/inc/ControlProperties.h index a7700b04958..faf5fc6a756 100644 --- a/src/cascadia/inc/ControlProperties.h +++ b/src/cascadia/inc/ControlProperties.h @@ -73,7 +73,8 @@ X(winrt::hstring, StartingDirectory) \ X(winrt::Microsoft::Terminal::Control::ScrollbarState, ScrollState, winrt::Microsoft::Terminal::Control::ScrollbarState::Visible) \ X(winrt::Microsoft::Terminal::Control::TextAntialiasingMode, AntialiasingMode, winrt::Microsoft::Terminal::Control::TextAntialiasingMode::Grayscale) \ - X(bool, ForceFullRepaintRendering, false) \ + X(winrt::Microsoft::Terminal::Control::GraphicsAPI, GraphicsAPI) \ + X(bool, DisablePartialInvalidation, false) \ X(bool, SoftwareRendering, false) \ X(bool, UseBackgroundImageForWindow, false) \ X(bool, ShowMarks, false) \ diff --git a/src/cppwinrt.build.pre.props b/src/cppwinrt.build.pre.props index ad36921e7b4..7fae29094ed 100644 --- a/src/cppwinrt.build.pre.props +++ b/src/cppwinrt.build.pre.props @@ -9,7 +9,6 @@ - AnyValueHereWillDisableTheOptOut true true en-US diff --git a/src/features.xml b/src/features.xml index 844b1cae459..18b99ba3163 100644 --- a/src/features.xml +++ b/src/features.xml @@ -168,6 +168,17 @@ + + Feature_ScratchpadPane + Allow the user to create scratchpad panes. Mostly just exists to validate non-terminal panes. + 997 + AlwaysDisabled + + Dev + Canary + + + Feature_KeypadModeEnabled Enables the DECKPAM, DECKPNM sequences to work as intended diff --git a/src/inc/til/string.h b/src/inc/til/string.h index e76639000cf..fcb6905e282 100644 --- a/src/inc/til/string.h +++ b/src/inc/til/string.h @@ -202,6 +202,26 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned" return to_ulong<>(str, base); } + // Implement to_int in terms of to_ulong by negating its result. to_ulong does not expect + // to be passed signed numbers and will return an error accordingly. That error when + // compared against -1 evaluates to true. We account for that by returning to_int_error if to_ulong + // returns an error. + constexpr int to_int(const std::wstring_view& str, unsigned long base = 0) noexcept + { + auto result = to_ulong_error; + const auto signPosition = str.find(L"-"); + const bool hasSign = signPosition != std::wstring_view::npos; + result = hasSign ? to_ulong(str.substr(signPosition + 1), base) : to_ulong(str, base); + + // Check that result is valid and will fit in an int. + if (result == to_ulong_error || (result > INT_MAX)) + { + return to_int_error; + } + + return hasSign ? result * -1 : result; + } + // Just like std::tolower, but without annoying locales. template constexpr T tolower_ascii(T c) @@ -376,6 +396,81 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned" return { beg, end }; } + // Splits a font-family list into individual font-families. It loosely follows the CSS spec for font-family. + // It splits by comma, handles quotes and simple escape characters, and it cleans whitespace. + // + // This is not the right place to put this, because it's highly specialized towards font-family names. + // But this code is needed both, in our renderer and in our settings UI. At the time I couldn't find a better place for it. + void iterate_font_families(const std::wstring_view& families, auto&& callback) + { + std::wstring family; + bool escape = false; + bool delayedSpace = false; + wchar_t stringType = 0; + + for (const auto ch : families) + { + if (!escape) + { + switch (ch) + { + case ' ': + if (stringType) + { + // Spaces are treated literally inside strings. + break; + } + delayedSpace = !family.empty(); + continue; + case '"': + case '\'': + if (stringType && stringType != ch) + { + // Single quotes inside double quotes are treated literally and vice versa. + break; + } + stringType = stringType == ch ? 0 : ch; + continue; + case ',': + if (stringType) + { + // Commas are treated literally inside strings. + break; + } + if (!family.empty()) + { + callback(std::move(family)); + family.clear(); + delayedSpace = false; + } + continue; + case '\\': + escape = true; + continue; + default: + break; + } + } + + // The `delayedSpace` logic automatically takes care for us to + // strip leading and trailing spaces and deduplicate them too. + if (delayedSpace) + { + delayedSpace = false; + family.push_back(L' '); + } + + family.push_back(ch); + escape = false; + } + + // Just like the comma handler above. + if (!stringType && !family.empty()) + { + callback(std::move(family)); + } + } + // This function is appropriate for case-insensitive equivalence testing of file paths and other "system" strings. // Similar to memcmp, this returns <0, 0 or >0. inline int compare_ordinal_insensitive(const std::wstring_view& lhs, const std::wstring_view& rhs) noexcept @@ -426,24 +521,4 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned" #pragma warning(suppress : 26477) // Use 'nullptr' rather than 0 or NULL (es.47). return FindNLSStringEx(LOCALE_NAME_USER_DEFAULT, LINGUISTIC_IGNORECASE, str.data(), strLen, needle.data(), needleLen, nullptr, nullptr, nullptr, 0) != -1; } - - // Implement to_int in terms of to_ulong by negating its result. to_ulong does not expect - // to be passed signed numbers and will return an error accordingly. That error when - // compared against -1 evaluates to true. We account for that by returning to_int_error if to_ulong - // returns an error. - constexpr int to_int(const std::wstring_view& str, unsigned long base = 0) noexcept - { - auto result = to_ulong_error; - const auto signPosition = str.find(L"-"); - const bool hasSign = signPosition != std::wstring_view::npos; - result = hasSign ? to_ulong(str.substr(signPosition + 1), base) : to_ulong(str, base); - - // Check that result is valid and will fit in an int. - if (result == to_ulong_error || (result > INT_MAX)) - { - return to_int_error; - } - - return hasSign ? result * -1 : result; - } } diff --git a/src/renderer/atlas/AtlasEngine.api.cpp b/src/renderer/atlas/AtlasEngine.api.cpp index a29c5e7779a..c031f67a154 100644 --- a/src/renderer/atlas/AtlasEngine.api.cpp +++ b/src/renderer/atlas/AtlasEngine.api.cpp @@ -264,7 +264,7 @@ try } #endif - _resolveFontMetrics(nullptr, fontInfoDesired, fontInfo); + _resolveFontMetrics(fontInfoDesired, fontInfo); return S_OK; } CATCH_RETURN() @@ -316,33 +316,18 @@ CATCH_RETURN() #pragma endregion -#pragma region DxRenderer - -HRESULT AtlasEngine::Enable() noexcept -{ - return S_OK; -} +#pragma region getter [[nodiscard]] std::wstring_view AtlasEngine::GetPixelShaderPath() noexcept { return _api.s->misc->customPixelShaderPath; } -[[nodiscard]] std::wstring_view AtlasEngine::GetPixelShaderImagePath() noexcept -{ - return _api.s->misc->customPixelShaderImagePath; -} - [[nodiscard]] bool AtlasEngine::GetRetroTerminalEffect() const noexcept { return _api.s->misc->useRetroTerminalEffect; } -[[nodiscard]] float AtlasEngine::GetScaling() const noexcept -{ - return static_cast(_api.s->font->dpi) / static_cast(USER_DEFAULT_SCREEN_DPI); -} - [[nodiscard]] Microsoft::Console::Types::Viewport AtlasEngine::GetViewportInCharacters(const Types::Viewport& viewInPixels) const noexcept { assert(_api.s->font->cellSize.x != 0); @@ -357,6 +342,10 @@ HRESULT AtlasEngine::Enable() noexcept return Types::Viewport::FromDimensions(viewInCharacters.Origin(), { viewInCharacters.Width() * _api.s->font->cellSize.x, viewInCharacters.Height() * _api.s->font->cellSize.y }); } +#pragma endregion + +#pragma region setter + void AtlasEngine::SetAntialiasingMode(const D2D1_TEXT_ANTIALIAS_MODE antialiasingMode) noexcept { const auto mode = static_cast(antialiasingMode); @@ -381,10 +370,6 @@ void AtlasEngine::EnableTransparentBackground(const bool isTransparent) noexcept } } -void AtlasEngine::SetForceFullRepaintRendering(bool enable) noexcept -{ -} - [[nodiscard]] HRESULT AtlasEngine::SetHwnd(const HWND hwnd) noexcept { if (_api.s->target->hwnd != hwnd) @@ -436,13 +421,29 @@ void AtlasEngine::SetSelectionBackground(const COLORREF color, const float alpha void AtlasEngine::SetSoftwareRendering(bool enable) noexcept { - if (_api.s->target->useSoftwareRendering != enable) + if (_api.s->target->useWARP != enable) { - _api.s.write()->target.write()->useSoftwareRendering = enable; + _api.s.write()->target.write()->useWARP = enable; } } -void AtlasEngine::SetWarningCallback(std::function pfn) noexcept +void AtlasEngine::SetDisablePartialInvalidation(bool enable) noexcept +{ + if (_api.s->target->disablePresent1 != enable) + { + _api.s.write()->target.write()->disablePresent1 = enable; + } +} + +void AtlasEngine::SetGraphicsAPI(GraphicsAPI graphicsAPI) noexcept +{ + if (_api.s->target->graphicsAPI != graphicsAPI) + { + _api.s.write()->target.write()->graphicsAPI = graphicsAPI; + } +} + +void AtlasEngine::SetWarningCallback(std::function pfn) noexcept { _p.warningCallback = std::move(pfn); } @@ -471,31 +472,39 @@ void AtlasEngine::SetWarningCallback(std::function pfn) noexcept [[nodiscard]] HRESULT AtlasEngine::UpdateFont(const FontInfoDesired& fontInfoDesired, FontInfo& fontInfo, const std::unordered_map& features, const std::unordered_map& axes) noexcept { - try - { - _updateFont(fontInfoDesired.GetFaceName().c_str(), fontInfoDesired, fontInfo, features, axes); - return S_OK; - } - CATCH_LOG(); - + // We're currently faced with a font caching bug that we're unable to reproduce locally. See GH#9375. + // But it occurs often enough and has no proper workarounds, so we're forced to fix it. + // + // Our leading theory is: When an app package has a + // (like Windows Terminal with Cascadia Mono/Code) and it gets updated, the system locks the file somehow. + // DirectWrite still has some information about the font cached though, so it thinks that it still exists, + // but using the font causes it to error out because it can't access it. This fact became apparent in + // commit 9e86c98 (PR #16196), because it showed that it's definitely not due to FindFamilyName() failing. + // + // The workaround is to catch the exception and retry it with our nearby fonts manually loaded in. if constexpr (Feature_NearbyFontLoading::IsEnabled()) { try { - // _resolveFontMetrics() checks `_api.s->font->fontCollection` for a pre-existing font collection, - // before falling back to using the system font collection. This way we can inject our custom one. See GH#9375. - // Doing it this way is a bit hacky, but it does have the benefit that we can cache a font collection - // instance across font changes, like when zooming the font size rapidly using the scroll wheel. - _api.s.write()->font.write()->fontCollection = FontCache::GetCached(); - _updateFont(fontInfoDesired.GetFaceName().c_str(), fontInfoDesired, fontInfo, features, axes); + _updateFont(fontInfoDesired, fontInfo, features, axes); return S_OK; } CATCH_LOG(); + + // _resolveFontMetrics() checks `_api.s->font->fontCollection` for a pre-existing font collection, + // before falling back to using the system font collection. This way we can inject our custom one. + // Doing it this way is a bit hacky, but it does have the benefit that we can cache a font collection + // instance across font changes, like when zooming the font size rapidly using the scroll wheel. + try + { + _api.s.write()->font.write()->fontCollection = FontCache::GetCached(); + } + CATCH_LOG(); } try { - _updateFont(nullptr, fontInfoDesired, fontInfo, features, axes); + _updateFont(fontInfoDesired, fontInfo, features, axes); return S_OK; } CATCH_RETURN(); @@ -527,7 +536,7 @@ void AtlasEngine::_resolveTransparencySettings() noexcept } } -void AtlasEngine::_updateFont(const wchar_t* faceName, const FontInfoDesired& fontInfoDesired, FontInfo& fontInfo, const std::unordered_map& features, const std::unordered_map& axes) +void AtlasEngine::_updateFont(const FontInfoDesired& fontInfoDesired, FontInfo& fontInfo, const std::unordered_map& features, const std::unordered_map& axes) { std::vector fontFeatures; if (!features.empty()) @@ -604,22 +613,19 @@ void AtlasEngine::_updateFont(const wchar_t* faceName, const FontInfoDesired& fo } const auto font = _api.s.write()->font.write(); - _resolveFontMetrics(faceName, fontInfoDesired, fontInfo, font); + _resolveFontMetrics(fontInfoDesired, fontInfo, font); font->fontFeatures = std::move(fontFeatures); font->fontAxisValues = std::move(fontAxisValues); } -void AtlasEngine::_resolveFontMetrics(const wchar_t* requestedFaceName, const FontInfoDesired& fontInfoDesired, FontInfo& fontInfo, FontSettings* fontMetrics) const +void AtlasEngine::_resolveFontMetrics(const FontInfoDesired& fontInfoDesired, FontInfo& fontInfo, FontSettings* fontMetrics) const { + const auto& faceName = fontInfoDesired.GetFaceName(); const auto requestedFamily = fontInfoDesired.GetFamily(); auto requestedWeight = fontInfoDesired.GetWeight(); auto fontSize = fontInfoDesired.GetFontSize(); auto requestedSize = fontInfoDesired.GetEngineSize(); - if (!requestedFaceName) - { - requestedFaceName = L"Consolas"; - } if (!requestedSize.height) { fontSize = 12.0f; @@ -640,22 +646,88 @@ void AtlasEngine::_resolveFontMetrics(const wchar_t* requestedFaceName, const Fo THROW_IF_FAILED(_p.dwriteFactory->GetSystemFontCollection(fontCollection.addressof(), FALSE)); } - u32 index = 0; - BOOL exists = false; - THROW_IF_FAILED(fontCollection->FindFamilyName(requestedFaceName, &index, &exists)); - THROW_HR_IF(DWRITE_E_NOFONT, !exists); + std::wstring primaryFontName; + std::wstring missingFontNames; + wil::com_ptr primaryFontFamily; + wil::com_ptr fontFallbackBuilder; + + // Resolves a comma-separated font list similar to CSS' font-family property. The first font in the list + // that can be resolved successfully will be the primary font which dictates the cell size among others. + // All remaining fonts are "secondary" fonts used for font fallback. + til::iterate_font_families(faceName, [&](std::wstring&& fontName) { + u32 index = 0; + BOOL exists = false; + THROW_IF_FAILED(fontCollection->FindFamilyName(fontName.c_str(), &index, &exists)); + + if (!exists) + { + if (!missingFontNames.empty()) + { + missingFontNames.append(L", "); + } + missingFontNames.append(fontName); + return; + } + + if (!primaryFontFamily) + { + primaryFontName = std::move(fontName); + THROW_IF_FAILED(fontCollection->GetFontFamily(index, primaryFontFamily.addressof())); + } + else + { + if (!fontFallbackBuilder) + { + THROW_IF_FAILED(_p.dwriteFactory->CreateFontFallbackBuilder(fontFallbackBuilder.addressof())); + } + + static constexpr DWRITE_UNICODE_RANGE fullRange{ 0, 0x10FFFF }; + auto fontNamePtr = fontName.c_str(); + THROW_IF_FAILED(fontFallbackBuilder->AddMapping( + /* ranges */ &fullRange, + /* rangesCount */ 1, + /* targetFamilyNames */ &fontNamePtr, + /* targetFamilyNamesCount */ 1, + /* fontCollection */ fontCollection.get(), + /* localeName */ nullptr, + /* baseFamilyName */ nullptr, + /* scale */ 1.0f)); + } + }); + + if (!missingFontNames.empty() && _p.warningCallback) + { + _p.warningCallback(DWRITE_E_NOFONT, missingFontNames); + } + + // Fall back to Consolas if no font was found or specified. + if (!primaryFontFamily) + { + primaryFontName = L"Consolas"; + + u32 index = 0; + BOOL exists = false; + THROW_IF_FAILED(fontCollection->FindFamilyName(primaryFontName.c_str(), &index, &exists)); + THROW_HR_IF(DWRITE_E_NOFONT, !exists); - wil::com_ptr fontFamily; - THROW_IF_FAILED(fontCollection->GetFontFamily(index, fontFamily.addressof())); + THROW_IF_FAILED(fontCollection->GetFontFamily(index, primaryFontFamily.addressof())); + } + + auto fontFallback = _api.systemFontFallback; + if (fontFallbackBuilder) + { + THROW_IF_FAILED(fontFallbackBuilder->AddMappings(_api.systemFontFallback.get())); + THROW_IF_FAILED(fontFallbackBuilder->CreateFontFallback(fontFallback.put())); + } - wil::com_ptr font; - THROW_IF_FAILED(fontFamily->GetFirstMatchingFont(static_cast(requestedWeight), DWRITE_FONT_STRETCH_NORMAL, DWRITE_FONT_STYLE_NORMAL, font.addressof())); + wil::com_ptr primaryFont; + THROW_IF_FAILED(primaryFontFamily->GetFirstMatchingFont(static_cast(requestedWeight), DWRITE_FONT_STRETCH_NORMAL, DWRITE_FONT_STYLE_NORMAL, primaryFont.addressof())); - wil::com_ptr fontFace; - THROW_IF_FAILED(font->CreateFontFace(fontFace.addressof())); + wil::com_ptr primaryFontFace; + THROW_IF_FAILED(primaryFont->CreateFontFace(primaryFontFace.addressof())); DWRITE_FONT_METRICS metrics{}; - fontFace->GetMetrics(&metrics); + primaryFontFace->GetMetrics(&metrics); // Point sizes are commonly treated at a 72 DPI scale // (including by OpenType), whereas DirectWrite uses 96 DPI. @@ -681,12 +753,12 @@ void AtlasEngine::_resolveFontMetrics(const wchar_t* requestedFaceName, const Fo static constexpr u32 codePoint = '0'; u16 glyphIndex; - THROW_IF_FAILED(fontFace->GetGlyphIndicesW(&codePoint, 1, &glyphIndex)); + THROW_IF_FAILED(primaryFontFace->GetGlyphIndicesW(&codePoint, 1, &glyphIndex)); if (glyphIndex) { DWRITE_GLYPH_METRICS glyphMetrics{}; - THROW_IF_FAILED(fontFace->GetDesignGlyphMetrics(&glyphIndex, 1, &glyphMetrics, FALSE)); + THROW_IF_FAILED(primaryFontFace->GetDesignGlyphMetrics(&glyphIndex, 1, &glyphMetrics, FALSE)); advanceWidth = static_cast(glyphMetrics.advanceWidth) * designUnitsPerPx; } } @@ -745,12 +817,11 @@ void AtlasEngine::_resolveFontMetrics(const wchar_t* requestedFaceName, const Fo requestedSize.width = gsl::narrow_cast(lrintf(fontSize / cellHeight * cellWidth)); } - fontInfo.SetFromEngine(requestedFaceName, requestedFamily, requestedWeight, false, coordSize, requestedSize); + fontInfo.SetFromEngine(primaryFontName, requestedFamily, requestedWeight, false, coordSize, requestedSize); } if (fontMetrics) { - std::wstring fontName{ requestedFaceName }; const auto fontWeightU16 = gsl::narrow_cast(requestedWeight); const auto advanceWidthU16 = gsl::narrow_cast(lrintf(advanceWidth)); const auto baselineU16 = gsl::narrow_cast(lrintf(baseline)); @@ -772,8 +843,9 @@ void AtlasEngine::_resolveFontMetrics(const wchar_t* requestedFaceName, const Fo // as we might cause _api to be in an inconsistent state otherwise. fontMetrics->fontCollection = std::move(fontCollection); - fontMetrics->fontFamily = std::move(fontFamily); - fontMetrics->fontName = std::move(fontName); + fontMetrics->fontFallback = std::move(fontFallback); + fontMetrics->fontFallback.try_query_to(fontMetrics->fontFallback1.put()); + fontMetrics->fontName = std::move(primaryFontName); fontMetrics->fontSize = fontSizeInPx; fontMetrics->cellSize = { cellWidth, cellHeight }; fontMetrics->fontWeight = fontWeightU16; diff --git a/src/renderer/atlas/AtlasEngine.cpp b/src/renderer/atlas/AtlasEngine.cpp index f7ed91b169a..8cfbe995a14 100644 --- a/src/renderer/atlas/AtlasEngine.cpp +++ b/src/renderer/atlas/AtlasEngine.cpp @@ -41,8 +41,7 @@ AtlasEngine::AtlasEngine() THROW_IF_FAILED(DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED, __uuidof(_p.dwriteFactory), reinterpret_cast<::IUnknown**>(_p.dwriteFactory.addressof()))); _p.dwriteFactory4 = _p.dwriteFactory.try_query(); - THROW_IF_FAILED(_p.dwriteFactory->GetSystemFontFallback(_p.systemFontFallback.addressof())); - _p.systemFontFallback1 = _p.systemFontFallback.try_query(); + THROW_IF_FAILED(_p.dwriteFactory->GetSystemFontFallback(_api.systemFontFallback.addressof())); wil::com_ptr textAnalyzer; THROW_IF_FAILED(_p.dwriteFactory->CreateTextAnalyzer(textAnalyzer.addressof())); @@ -550,7 +549,7 @@ void AtlasEngine::_handleSettingsUpdate() if (targetChanged) { - // target->useSoftwareRendering affects the selection of our IDXGIAdapter which requires us to reset _p.dxgi. + // target->useWARP affects the selection of our IDXGIAdapter which requires us to reset _p.dxgi. // This will indirectly also recreate the backend, when AtlasEngine::_recreateAdapter() detects this change. _p.dxgi = {}; } @@ -575,7 +574,7 @@ void AtlasEngine::_recreateFontDependentResources() { wchar_t localeName[LOCALE_NAME_MAX_LENGTH]; - if (FAILED(GetUserDefaultLocaleName(&localeName[0], LOCALE_NAME_MAX_LENGTH))) + if (!GetUserDefaultLocaleName(&localeName[0], LOCALE_NAME_MAX_LENGTH)) { memcpy(&localeName[0], L"en-US", 12); } @@ -841,7 +840,7 @@ void AtlasEngine::_mapCharacters(const wchar_t* text, const u32 textLength, u32* if (textFormatAxis) { - THROW_IF_FAILED(_p.systemFontFallback1->MapCharacters( + THROW_IF_FAILED(_p.s->font->fontFallback1->MapCharacters( /* analysisSource */ &analysisSource, /* textPosition */ 0, /* textLength */ textLength, @@ -859,7 +858,7 @@ void AtlasEngine::_mapCharacters(const wchar_t* text, const u32 textLength, u32* const auto baseStyle = WI_IsFlagSet(_api.attributes, FontRelevantAttributes::Italic) ? DWRITE_FONT_STYLE_ITALIC : DWRITE_FONT_STYLE_NORMAL; wil::com_ptr font; - THROW_IF_FAILED(_p.systemFontFallback->MapCharacters( + THROW_IF_FAILED(_p.s->font->fontFallback->MapCharacters( /* analysisSource */ &analysisSource, /* textPosition */ 0, /* textLength */ textLength, diff --git a/src/renderer/atlas/AtlasEngine.h b/src/renderer/atlas/AtlasEngine.h index 1c238dad048..5c9f954ae2f 100644 --- a/src/renderer/atlas/AtlasEngine.h +++ b/src/renderer/atlas/AtlasEngine.h @@ -57,31 +57,29 @@ namespace Microsoft::Console::Render::Atlas [[nodiscard]] HRESULT GetFontSize(_Out_ til::size* pFontSize) noexcept override; [[nodiscard]] HRESULT IsGlyphWideByFont(std::wstring_view glyph, _Out_ bool* pResult) noexcept override; [[nodiscard]] HRESULT UpdateTitle(std::wstring_view newTitle) noexcept override; - - // DxRenderer - getter - HRESULT Enable() noexcept override; - [[nodiscard]] std::wstring_view GetPixelShaderPath() noexcept override; - [[nodiscard]] std::wstring_view GetPixelShaderImagePath() noexcept override; - [[nodiscard]] bool GetRetroTerminalEffect() const noexcept override; - [[nodiscard]] float GetScaling() const noexcept override; - [[nodiscard]] Types::Viewport GetViewportInCharacters(const Types::Viewport& viewInPixels) const noexcept override; - [[nodiscard]] Types::Viewport GetViewportInPixels(const Types::Viewport& viewInCharacters) const noexcept override; - // DxRenderer - setter - void SetAntialiasingMode(D2D1_TEXT_ANTIALIAS_MODE antialiasingMode) noexcept override; - void SetCallback(std::function pfn) noexcept override; - void EnableTransparentBackground(const bool isTransparent) noexcept override; - void SetForceFullRepaintRendering(bool enable) noexcept override; - [[nodiscard]] HRESULT SetHwnd(HWND hwnd) noexcept override; - void SetPixelShaderPath(std::wstring_view value) noexcept override; - void SetPixelShaderImagePath(std::wstring_view value) noexcept override; - void SetRetroTerminalEffect(bool enable) noexcept override; - void SetSelectionBackground(COLORREF color, float alpha = 0.5f) noexcept override; - void SetSoftwareRendering(bool enable) noexcept override; - void SetWarningCallback(std::function pfn) noexcept override; - [[nodiscard]] HRESULT SetWindowSize(til::size pixels) noexcept override; - [[nodiscard]] HRESULT UpdateFont(const FontInfoDesired& pfiFontInfoDesired, FontInfo& fiFontInfo, const std::unordered_map& features, const std::unordered_map& axes) noexcept override; void UpdateHyperlinkHoveredId(uint16_t hoveredId) noexcept override; + // getter + [[nodiscard]] std::wstring_view GetPixelShaderPath() noexcept; + [[nodiscard]] bool GetRetroTerminalEffect() const noexcept; + [[nodiscard]] Types::Viewport GetViewportInCharacters(const Types::Viewport& viewInPixels) const noexcept; + [[nodiscard]] Types::Viewport GetViewportInPixels(const Types::Viewport& viewInCharacters) const noexcept; + // setter + void SetAntialiasingMode(D2D1_TEXT_ANTIALIAS_MODE antialiasingMode) noexcept; + void SetCallback(std::function pfn) noexcept; + void EnableTransparentBackground(const bool isTransparent) noexcept; + [[nodiscard]] HRESULT SetHwnd(HWND hwnd) noexcept; + void SetPixelShaderPath(std::wstring_view value) noexcept; + void SetPixelShaderImagePath(std::wstring_view value) noexcept; + void SetRetroTerminalEffect(bool enable) noexcept; + void SetSelectionBackground(COLORREF color, float alpha = 0.5f) noexcept; + void SetSoftwareRendering(bool enable) noexcept; + void SetDisablePartialInvalidation(bool enable) noexcept; + void SetGraphicsAPI(GraphicsAPI graphicsAPI) noexcept; + void SetWarningCallback(std::function pfn) noexcept; + [[nodiscard]] HRESULT SetWindowSize(til::size pixels) noexcept; + [[nodiscard]] HRESULT UpdateFont(const FontInfoDesired& pfiFontInfoDesired, FontInfo& fiFontInfo, const std::unordered_map& features, const std::unordered_map& axes) noexcept; + private: // AtlasEngine.cpp ATLAS_ATTR_COLD void _handleSettingsUpdate(); @@ -96,8 +94,8 @@ namespace Microsoft::Console::Render::Atlas // AtlasEngine.api.cpp void _resolveTransparencySettings() noexcept; - void _updateFont(const wchar_t* faceName, const FontInfoDesired& fontInfoDesired, FontInfo& fontInfo, const std::unordered_map& features, const std::unordered_map& axes); - void _resolveFontMetrics(const wchar_t* faceName, const FontInfoDesired& fontInfoDesired, FontInfo& fontInfo, FontSettings* fontMetrics = nullptr) const; + void _updateFont(const FontInfoDesired& fontInfoDesired, FontInfo& fontInfo, const std::unordered_map& features, const std::unordered_map& axes); + void _resolveFontMetrics(const FontInfoDesired& fontInfoDesired, FontInfo& fontInfo, FontSettings* fontMetrics = nullptr) const; // AtlasEngine.r.cpp ATLAS_ATTR_COLD void _recreateAdapter(); @@ -158,6 +156,7 @@ namespace Microsoft::Console::Render::Atlas Buffer glyphAdvances; Buffer glyphOffsets; + wil::com_ptr systemFontFallback; wil::com_ptr replacementCharacterFontFace; u16 replacementCharacterGlyphIndex = 0; bool replacementCharacterLookedUp = false; diff --git a/src/renderer/atlas/AtlasEngine.r.cpp b/src/renderer/atlas/AtlasEngine.r.cpp index 0c546a8dceb..f0a448d491b 100644 --- a/src/renderer/atlas/AtlasEngine.r.cpp +++ b/src/renderer/atlas/AtlasEngine.r.cpp @@ -65,7 +65,7 @@ catch (const wil::ResultException& exception) { try { - _p.warningCallback(hr); + _p.warningCallback(hr, {}); } CATCH_LOG() } @@ -134,7 +134,7 @@ void AtlasEngine::_recreateAdapter() DXGI_ADAPTER_DESC1 desc{}; { - const auto useSoftwareRendering = _p.s->target->useSoftwareRendering; + const auto useWARP = _p.s->target->useWARP; UINT index = 0; do @@ -142,13 +142,13 @@ void AtlasEngine::_recreateAdapter() THROW_IF_FAILED(_p.dxgi.factory->EnumAdapters1(index++, adapter.put())); THROW_IF_FAILED(adapter->GetDesc1(&desc)); - // If useSoftwareRendering is false we exit during the first iteration. Using the default adapter (index 0) + // If useWARP is false we exit during the first iteration. Using the default adapter (index 0) // is the right thing to do under most circumstances, unless you _really_ want to get your hands dirty. // The alternative is to track the window rectangle in respect to all IDXGIOutputs and select the right // IDXGIAdapter, while also considering the "graphics preference" override in the windows settings app, etc. // - // If useSoftwareRendering is true we search until we find the first WARP adapter (usually the last adapter). - } while (useSoftwareRendering && WI_IsFlagClear(desc.Flags, DXGI_ADAPTER_FLAG_SOFTWARE)); + // If useWARP is true we search until we find the first WARP adapter (usually the last adapter). + } while (useWARP && WI_IsFlagClear(desc.Flags, DXGI_ADAPTER_FLAG_SOFTWARE)); } if (memcmp(&_p.dxgi.adapterLuid, &desc.AdapterLuid, sizeof(LUID)) != 0) @@ -166,7 +166,8 @@ void AtlasEngine::_recreateBackend() // HWND, IWindow, or composition surface at a time. --> Destroy it while we still have the old device. _destroySwapChain(); - auto d2dMode = ATLAS_DEBUG_FORCE_D2D_MODE; + auto graphicsAPI = _p.s->target->graphicsAPI; + auto deviceFlags = D3D11_CREATE_DEVICE_SINGLETHREADED #ifndef NDEBUG @@ -182,8 +183,14 @@ void AtlasEngine::_recreateBackend() if (WI_IsFlagSet(_p.dxgi.adapterFlags, DXGI_ADAPTER_FLAG_SOFTWARE)) { + // If we're using WARP we don't want to disable those optimizations of course. WI_ClearFlag(deviceFlags, D3D11_CREATE_DEVICE_PREVENT_INTERNAL_THREADING_OPTIMIZATIONS); - d2dMode = true; + + // I'm not sure whether Direct2D is actually faster on WARP, but it's definitely better tested. + if (graphicsAPI == GraphicsAPI::Automatic) + { + graphicsAPI = GraphicsAPI::Direct2D; + } } wil::com_ptr device0; @@ -202,17 +209,16 @@ void AtlasEngine::_recreateBackend() #pragma warning(suppress : 26496) // The variable 'hr' does not change after construction, mark it as const (con.4). auto hr = D3D11CreateDevice( - /* pAdapter */ _p.dxgi.adapter.get(), - /* DriverType */ D3D_DRIVER_TYPE_UNKNOWN, - /* Software */ nullptr, - /* Flags */ deviceFlags, - /* pFeatureLevels */ featureLevels.data(), - /* FeatureLevels */ gsl::narrow_cast(featureLevels.size()), - /* SDKVersion */ D3D11_SDK_VERSION, - /* ppDevice */ device0.put(), - /* pFeatureLevel */ &featureLevel, + /* pAdapter */ _p.dxgi.adapter.get(), + /* DriverType */ D3D_DRIVER_TYPE_UNKNOWN, + /* Software */ nullptr, + /* Flags */ deviceFlags, + /* pFeatureLevels */ featureLevels.data(), + /* FeatureLevels */ gsl::narrow_cast(featureLevels.size()), + /* SDKVersion */ D3D11_SDK_VERSION, + /* ppDevice */ device0.put(), + /* pFeatureLevel */ &featureLevel, /* ppImmediateContext */ deviceContext0.put()); - #ifndef NDEBUG if (hr == DXGI_ERROR_SDK_COMPONENT_MISSING) { @@ -222,15 +228,15 @@ void AtlasEngine::_recreateBackend() WI_ClearFlag(deviceFlags, D3D11_CREATE_DEVICE_DEBUG); hr = D3D11CreateDevice( - /* pAdapter */ _p.dxgi.adapter.get(), - /* DriverType */ D3D_DRIVER_TYPE_UNKNOWN, - /* Software */ nullptr, - /* Flags */ deviceFlags, - /* pFeatureLevels */ featureLevels.data(), - /* FeatureLevels */ gsl::narrow_cast(featureLevels.size()), - /* SDKVersion */ D3D11_SDK_VERSION, - /* ppDevice */ device0.put(), - /* pFeatureLevel */ &featureLevel, + /* pAdapter */ _p.dxgi.adapter.get(), + /* DriverType */ D3D_DRIVER_TYPE_UNKNOWN, + /* Software */ nullptr, + /* Flags */ deviceFlags, + /* pFeatureLevels */ featureLevels.data(), + /* FeatureLevels */ gsl::narrow_cast(featureLevels.size()), + /* SDKVersion */ D3D11_SDK_VERSION, + /* ppDevice */ device0.put(), + /* pFeatureLevel */ &featureLevel, /* ppImmediateContext */ deviceContext0.put()); } #endif @@ -252,37 +258,44 @@ void AtlasEngine::_recreateBackend() } #endif - if (featureLevel < D3D_FEATURE_LEVEL_10_0) - { - d2dMode = true; - } - else if (featureLevel < D3D_FEATURE_LEVEL_11_0) + if (graphicsAPI == GraphicsAPI::Automatic) { - D3D11_FEATURE_DATA_D3D10_X_HARDWARE_OPTIONS options{}; - // I'm assuming if `CheckFeatureSupport` fails, it'll leave `options` untouched which will result in `d2dMode |= true`. - std::ignore = device->CheckFeatureSupport(D3D11_FEATURE_D3D10_X_HARDWARE_OPTIONS, &options, sizeof(options)); - d2dMode |= !options.ComputeShaders_Plus_RawAndStructuredBuffers_Via_Shader_4_x; + if (featureLevel < D3D_FEATURE_LEVEL_10_0) + { + graphicsAPI = GraphicsAPI::Direct2D; + } + else if (featureLevel < D3D_FEATURE_LEVEL_11_0) + { + D3D11_FEATURE_DATA_D3D10_X_HARDWARE_OPTIONS options{}; + if (FAILED(device->CheckFeatureSupport(D3D11_FEATURE_D3D10_X_HARDWARE_OPTIONS, &options, sizeof(options))) || + !options.ComputeShaders_Plus_RawAndStructuredBuffers_Via_Shader_4_x) + { + graphicsAPI = GraphicsAPI::Direct2D; + } + } } _p.device = std::move(device); _p.deviceContext = std::move(deviceContext); - if (d2dMode) + switch (graphicsAPI) { + case GraphicsAPI::Direct2D: _b = std::make_unique(); - } - else - { + _hackIsBackendD2D = true; + break; + default: _b = std::make_unique(_p); + _hackIsBackendD2D = false; + break; } // This ensures that the backends redraw their entire viewports whenever a new swap chain is created, // EVEN IF we got called when no actual settings changed (i.e. rendering failure, etc.). _p.MarkAllAsDirty(); - const auto hackWantsBuiltinGlyphs = _p.s->font->builtinGlyphs && !d2dMode; + const auto hackWantsBuiltinGlyphs = _p.s->font->builtinGlyphs && !_hackIsBackendD2D; _hackTriggerRedrawAll = _hackWantsBuiltinGlyphs != hackWantsBuiltinGlyphs; - _hackIsBackendD2D = d2dMode; _hackWantsBuiltinGlyphs = hackWantsBuiltinGlyphs; } @@ -330,7 +343,7 @@ void AtlasEngine::_createSwapChain() // more "intelligent" composition and display updates to occur like Panel Self Refresh // (PSR) which requires dirty rectangles (Present1 API) to work correctly. // We were asked by DWM folks to use DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL for this reason (PSR). - .SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL, + .SwapEffect = _p.s->target->disablePresent1 ? DXGI_SWAP_EFFECT_FLIP_DISCARD : DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL, // If our background is opaque we can enable "independent" flips by setting DXGI_ALPHA_MODE_IGNORE. // As our swap chain won't have to compose with DWM anymore it reduces the display latency dramatically. .AlphaMode = _p.s->target->useAlpha ? DXGI_ALPHA_MODE_PREMULTIPLIED : DXGI_ALPHA_MODE_IGNORE, @@ -458,44 +471,39 @@ void AtlasEngine::_present() return; } - if constexpr (!ATLAS_DEBUG_SHOW_DIRTY) + if (!ATLAS_DEBUG_SHOW_DIRTY && !_p.s->target->disablePresent1 && memcmp(&dirtyRect, &fullRect, sizeof(RECT)) != 0) { - if (memcmp(&dirtyRect, &fullRect, sizeof(RECT)) != 0) - { - params.DirtyRectsCount = 1; - params.pDirtyRects = &dirtyRect; + params.DirtyRectsCount = 1; + params.pDirtyRects = &dirtyRect; - if (_p.scrollOffset) - { - const auto offsetInPx = _p.scrollOffset * _p.s->font->cellSize.y; - const auto width = _p.s->targetSize.x; - // We don't use targetSize.y here, because "height" refers to the bottom coordinate of the last text row - // in the buffer. We then add the "offsetInPx" (which is negative when scrolling text upwards) and thus - // end up with a "bottom" value that is the bottom of the last row of text that we haven't invalidated. - const auto height = _p.s->viewportCellCount.y * _p.s->font->cellSize.y; - const auto top = std::max(0, offsetInPx); - const auto bottom = height + std::min(0, offsetInPx); - - scrollRect = { 0, top, width, bottom }; - scrollOffset = { 0, offsetInPx }; - - params.pScrollRect = &scrollRect; - params.pScrollOffset = &scrollOffset; - } + if (_p.scrollOffset) + { + const auto offsetInPx = _p.scrollOffset * _p.s->font->cellSize.y; + const auto width = _p.s->targetSize.x; + // We don't use targetSize.y here, because "height" refers to the bottom coordinate of the last text row + // in the buffer. We then add the "offsetInPx" (which is negative when scrolling text upwards) and thus + // end up with a "bottom" value that is the bottom of the last row of text that we haven't invalidated. + const auto height = _p.s->viewportCellCount.y * _p.s->font->cellSize.y; + const auto top = std::max(0, offsetInPx); + const auto bottom = height + std::min(0, offsetInPx); + + scrollRect = { 0, top, width, bottom }; + scrollOffset = { 0, offsetInPx }; + + params.pScrollRect = &scrollRect; + params.pScrollOffset = &scrollOffset; } } + auto hr = _p.swapChain.swapChain->Present1(1, 0, ¶ms); if constexpr (Feature_AtlasEnginePresentFallback::IsEnabled()) { - if (FAILED_LOG(_p.swapChain.swapChain->Present1(1, 0, ¶ms))) + if (FAILED(hr) && params.DirtyRectsCount != 0) { - THROW_IF_FAILED(_p.swapChain.swapChain->Present(1, 0)); + hr = _p.swapChain.swapChain->Present(1, 0); } } - else - { - THROW_IF_FAILED(_p.swapChain.swapChain->Present1(1, 0, ¶ms)); - } + THROW_IF_FAILED(hr); _p.swapChain.waitForPresentation = true; } diff --git a/src/renderer/atlas/Backend.h b/src/renderer/atlas/Backend.h index cdeab63b12c..65da06acb93 100644 --- a/src/renderer/atlas/Backend.h +++ b/src/renderer/atlas/Backend.h @@ -30,9 +30,6 @@ namespace Microsoft::Console::Render::Atlas // This helps with benchmarking the application as it'll run beyond display refresh rate. #define ATLAS_DEBUG_DISABLE_FRAME_LATENCY_WAITABLE_OBJECT 0 - // Forces the use of Direct2D for text rendering (= BackendD2D). -#define ATLAS_DEBUG_FORCE_D2D_MODE 0 - // Adds an artificial delay before every render pass. In milliseconds. #define ATLAS_DEBUG_RENDER_DELAY 0 diff --git a/src/renderer/atlas/BackendD3D.cpp b/src/renderer/atlas/BackendD3D.cpp index 19e98c6557e..6d7a86c5e4d 100644 --- a/src/renderer/atlas/BackendD3D.cpp +++ b/src/renderer/atlas/BackendD3D.cpp @@ -432,7 +432,7 @@ void BackendD3D::_recreateCustomShader(const RenderingPayload& p) } if (p.warningCallback) { - p.warningCallback(D2DERR_SHADER_COMPILE_FAILED); + p.warningCallback(D2DERR_SHADER_COMPILE_FAILED, p.s->misc->customPixelShaderPath); } } @@ -448,7 +448,7 @@ void BackendD3D::_recreateCustomShader(const RenderingPayload& p) _customPixelShader.reset(); if (p.warningCallback) { - p.warningCallback(D2DERR_SHADER_COMPILE_FAILED); + p.warningCallback(D2DERR_SHADER_COMPILE_FAILED, p.s->misc->customPixelShaderImagePath); } } } @@ -1439,7 +1439,7 @@ BackendD3D::AtlasGlyphEntry* BackendD3D::_drawBuiltinGlyph(const RenderingPayloa if (BuiltinGlyphs::IsSoftFontChar(glyphIndex)) { - _drawSoftFontGlyph(p, r, glyphIndex); + shadingType = _drawSoftFontGlyph(p, r, glyphIndex); } else { @@ -1465,22 +1465,17 @@ BackendD3D::AtlasGlyphEntry* BackendD3D::_drawBuiltinGlyph(const RenderingPayloa return glyphEntry; } -void BackendD3D::_drawSoftFontGlyph(const RenderingPayload& p, const D2D1_RECT_F& rect, u32 glyphIndex) +BackendD3D::ShadingType BackendD3D::_drawSoftFontGlyph(const RenderingPayload& p, const D2D1_RECT_F& rect, u32 glyphIndex) { - _d2dRenderTarget->PushAxisAlignedClip(&rect, D2D1_ANTIALIAS_MODE_ALIASED); - const auto restoreD2D = wil::scope_exit([&]() { - _d2dRenderTarget->PopAxisAlignedClip(); - }); - const auto width = static_cast(p.s->font->softFontCellSize.width); const auto height = static_cast(p.s->font->softFontCellSize.height); const auto softFontIndex = glyphIndex - 0xEF20u; const auto data = til::clamp_slice_len(p.s->font->softFontPattern, height * softFontIndex, height); + // This happens if someone wrote a U+EF2x character (by accident), but we don't even have soft fonts enabled yet. if (data.empty() || data.size() != height) { - _d2dRenderTarget->Clear(); - return; + return ShadingType::Default; } if (!_softFontBitmap) @@ -1517,8 +1512,14 @@ void BackendD3D::_drawSoftFontGlyph(const RenderingPayload& p, const D2D1_RECT_F THROW_IF_FAILED(_softFontBitmap->CopyFromMemory(nullptr, bitmapData.data(), pitch)); } + _d2dRenderTarget->PushAxisAlignedClip(&rect, D2D1_ANTIALIAS_MODE_ALIASED); + const auto restoreD2D = wil::scope_exit([&]() { + _d2dRenderTarget->PopAxisAlignedClip(); + }); + const auto interpolation = p.s->font->antialiasingMode == AntialiasingMode::Aliased ? D2D1_INTERPOLATION_MODE_NEAREST_NEIGHBOR : D2D1_INTERPOLATION_MODE_HIGH_QUALITY_CUBIC; _d2dRenderTarget->DrawBitmap(_softFontBitmap.get(), &rect, 1, interpolation, nullptr, nullptr); + return ShadingType::TextGrayscale; } void BackendD3D::_drawGlyphAtlasAllocate(const RenderingPayload& p, stbrp_rect& rect) diff --git a/src/renderer/atlas/BackendD3D.h b/src/renderer/atlas/BackendD3D.h index 5e8055aecd0..25833b831dc 100644 --- a/src/renderer/atlas/BackendD3D.h +++ b/src/renderer/atlas/BackendD3D.h @@ -215,7 +215,7 @@ namespace Microsoft::Console::Render::Atlas ATLAS_ATTR_COLD void _drawTextOverlapSplit(const RenderingPayload& p, u16 y); [[nodiscard]] ATLAS_ATTR_COLD AtlasGlyphEntry* _drawGlyph(const RenderingPayload& p, const ShapedRow& row, AtlasFontFaceEntry& fontFaceEntry, u32 glyphIndex); AtlasGlyphEntry* _drawBuiltinGlyph(const RenderingPayload& p, const ShapedRow& row, AtlasFontFaceEntry& fontFaceEntry, u32 glyphIndex); - void _drawSoftFontGlyph(const RenderingPayload& p, const D2D1_RECT_F& rect, u32 glyphIndex); + ShadingType _drawSoftFontGlyph(const RenderingPayload& p, const D2D1_RECT_F& rect, u32 glyphIndex); void _drawGlyphAtlasAllocate(const RenderingPayload& p, stbrp_rect& rect); static AtlasGlyphEntry* _drawGlyphAllocateEntry(const ShapedRow& row, AtlasFontFaceEntry& fontFaceEntry, u32 glyphIndex); static void _splitDoubleHeightGlyph(const RenderingPayload& p, const ShapedRow& row, AtlasFontFaceEntry& fontFaceEntry, AtlasGlyphEntry* glyphEntry); diff --git a/src/renderer/atlas/common.h b/src/renderer/atlas/common.h index 967191bdfbe..ad9bf2587be 100644 --- a/src/renderer/atlas/common.h +++ b/src/renderer/atlas/common.h @@ -311,11 +311,20 @@ namespace Microsoft::Console::Render::Atlas DWRITE_SCRIPT_ANALYSIS analysis; }; + enum class GraphicsAPI + { + Automatic, + Direct2D, + Direct3D11, + }; + struct TargetSettings { HWND hwnd = nullptr; bool useAlpha = false; - bool useSoftwareRendering = false; + bool useWARP = false; + bool disablePresent1 = false; + GraphicsAPI graphicsAPI = GraphicsAPI::Automatic; }; enum class AntialiasingMode : u8 @@ -336,7 +345,8 @@ namespace Microsoft::Console::Render::Atlas struct FontSettings { wil::com_ptr fontCollection; - wil::com_ptr fontFamily; + wil::com_ptr fontFallback; + wil::com_ptr fontFallback1; // optional, might be nullptr std::wstring fontName; std::vector fontFeatures; std::vector fontAxisValues; @@ -479,10 +489,8 @@ namespace Microsoft::Console::Render::Atlas wil::com_ptr d2dFactory; wil::com_ptr dwriteFactory; wil::com_ptr dwriteFactory4; // optional, might be nullptr - wil::com_ptr systemFontFallback; - wil::com_ptr systemFontFallback1; // optional, might be nullptr wil::com_ptr textAnalyzer; - std::function warningCallback; + std::function warningCallback; std::function swapChainChangedCallback; //// Parameters which are constant for the existence of the backend. diff --git a/src/renderer/base/RenderEngineBase.cpp b/src/renderer/base/RenderEngineBase.cpp index 80c1f86cda1..224ff155f35 100644 --- a/src/renderer/base/RenderEngineBase.cpp +++ b/src/renderer/base/RenderEngineBase.cpp @@ -77,6 +77,10 @@ void RenderEngineBase::WaitUntilCanRender() noexcept Sleep(8); } +void RenderEngineBase::UpdateHyperlinkHoveredId(const uint16_t /*hoveredId*/) noexcept +{ +} + // Routine Description: // - Notifies us that we're about to circle the buffer, giving us a chance to // force a repaint before the buffer contents are lost. diff --git a/src/renderer/inc/IRenderEngine.hpp b/src/renderer/inc/IRenderEngine.hpp index 2d151364463..5dcaf17685a 100644 --- a/src/renderer/inc/IRenderEngine.hpp +++ b/src/renderer/inc/IRenderEngine.hpp @@ -90,33 +90,7 @@ namespace Microsoft::Console::Render [[nodiscard]] virtual HRESULT GetFontSize(_Out_ til::size* pFontSize) noexcept = 0; [[nodiscard]] virtual HRESULT IsGlyphWideByFont(std::wstring_view glyph, _Out_ bool* pResult) noexcept = 0; [[nodiscard]] virtual HRESULT UpdateTitle(std::wstring_view newTitle) noexcept = 0; - - // The following functions used to be specific to the DxRenderer and they should - // be abstracted away and integrated into the above or simply get removed. - - // DxRenderer - getter - virtual HRESULT Enable() noexcept { return S_OK; } - [[nodiscard]] virtual std::wstring_view GetPixelShaderPath() noexcept { return {}; } - [[nodiscard]] virtual std::wstring_view GetPixelShaderImagePath() noexcept { return {}; } - [[nodiscard]] virtual bool GetRetroTerminalEffect() const noexcept { return false; } - [[nodiscard]] virtual float GetScaling() const noexcept { return 1; } - [[nodiscard]] virtual Types::Viewport GetViewportInCharacters(const Types::Viewport& viewInPixels) const noexcept { return Types::Viewport::Empty(); } - [[nodiscard]] virtual Types::Viewport GetViewportInPixels(const Types::Viewport& viewInCharacters) const noexcept { return Types::Viewport::Empty(); } - // DxRenderer - setter - virtual void SetAntialiasingMode(const D2D1_TEXT_ANTIALIAS_MODE antialiasingMode) noexcept {} - virtual void SetCallback(std::function pfn) noexcept {} - virtual void EnableTransparentBackground(const bool isTransparent) noexcept {} - virtual void SetForceFullRepaintRendering(bool enable) noexcept {} - [[nodiscard]] virtual HRESULT SetHwnd(const HWND hwnd) noexcept { return E_NOTIMPL; } - virtual void SetPixelShaderPath(std::wstring_view value) noexcept {} - virtual void SetPixelShaderImagePath(std::wstring_view value) noexcept {} - virtual void SetRetroTerminalEffect(bool enable) noexcept {} - virtual void SetSelectionBackground(const COLORREF color, const float alpha = 0.5f) noexcept {} - virtual void SetSoftwareRendering(bool enable) noexcept {} - virtual void SetWarningCallback(std::function pfn) noexcept {} - [[nodiscard]] virtual HRESULT SetWindowSize(const til::size pixels) noexcept { return E_NOTIMPL; } - [[nodiscard]] virtual HRESULT UpdateFont(const FontInfoDesired& pfiFontInfoDesired, FontInfo& fiFontInfo, const std::unordered_map& features, const std::unordered_map& axes) noexcept { return E_NOTIMPL; } - virtual void UpdateHyperlinkHoveredId(const uint16_t hoveredId) noexcept {} + virtual void UpdateHyperlinkHoveredId(const uint16_t hoveredId) noexcept = 0; }; } #pragma warning(pop) diff --git a/src/renderer/inc/RenderEngineBase.hpp b/src/renderer/inc/RenderEngineBase.hpp index 8c33d59b1e7..6390f5fe7f6 100644 --- a/src/renderer/inc/RenderEngineBase.hpp +++ b/src/renderer/inc/RenderEngineBase.hpp @@ -46,6 +46,7 @@ namespace Microsoft::Console::Render [[nodiscard]] HRESULT InvalidateFlush(_In_ const bool circled, _Out_ bool* const pForcePaint) noexcept override; void WaitUntilCanRender() noexcept override; + void UpdateHyperlinkHoveredId(const uint16_t hoveredId) noexcept override; protected: [[nodiscard]] virtual HRESULT _DoUpdateTitle(const std::wstring_view newTitle) noexcept = 0; diff --git a/src/renderer/uia/UiaRenderer.hpp b/src/renderer/uia/UiaRenderer.hpp index bd1734c5d64..5d244dcc130 100644 --- a/src/renderer/uia/UiaRenderer.hpp +++ b/src/renderer/uia/UiaRenderer.hpp @@ -30,7 +30,7 @@ namespace Microsoft::Console::Render // Only one UiaEngine may present information at a time. // This ensures that an automation client isn't overwhelmed // by events when there are multiple TermControls - [[nodiscard]] HRESULT Enable() noexcept override; + [[nodiscard]] HRESULT Enable() noexcept; [[nodiscard]] HRESULT Disable() noexcept; // IRenderEngine Members diff --git a/src/renderer/wddmcon/WddmConRenderer.hpp b/src/renderer/wddmcon/WddmConRenderer.hpp index 930fbe6dea6..f37146daf5b 100644 --- a/src/renderer/wddmcon/WddmConRenderer.hpp +++ b/src/renderer/wddmcon/WddmConRenderer.hpp @@ -19,7 +19,7 @@ namespace Microsoft::Console::Render // Used to release device resources so that another instance of // conhost can render to the screen (i.e. only one DirectX // application may control the screen at a time.) - [[nodiscard]] HRESULT Enable() noexcept override; + [[nodiscard]] HRESULT Enable() noexcept; [[nodiscard]] HRESULT Disable() noexcept; til::rect GetDisplaySize() noexcept; diff --git a/src/til/ut_til/string.cpp b/src/til/ut_til/string.cpp index 26fbf92ea47..2ef656a04d7 100644 --- a/src/til/ut_til/string.cpp +++ b/src/til/ut_til/string.cpp @@ -7,6 +7,25 @@ using namespace WEX::Common; using namespace WEX::Logging; using namespace WEX::TestExecution; +template<> +class WEX::TestExecution::VerifyOutputTraits> +{ +public: + static WEX::Common::NoThrowString ToString(const std::vector& vec) + { + WEX::Common::NoThrowString str; + str.Append(L"{ "); + for (size_t i = 0; i < vec.size(); ++i) + { + str.Append(i == 0 ? L"\"" : L", \""); + str.Append(vec[i].c_str()); + str.Append(L"\""); + } + str.Append(L" }"); + return str; + } +}; + class StringTests { TEST_CLASS(StringTests); @@ -199,4 +218,24 @@ class StringTests VERIFY_IS_TRUE(til::is_legal_path(LR"(C:\Users\Documents and Settings\Users\;\Why not)")); VERIFY_IS_FALSE(til::is_legal_path(LR"(C:\Users\Documents and Settings\"Quote-un-quote users")")); } + + TEST_METHOD(IterateFontFamilies) + { + static constexpr auto expected = [](auto&&... args) { + return std::vector{ std::forward(args)... }; + }; + static constexpr auto actual = [](std::wstring_view families) { + std::vector split; + til::iterate_font_families(families, [&](std::wstring&& str) { + split.emplace_back(std::move(str)); + }); + return split; + }; + + VERIFY_ARE_EQUAL(expected(L"foo", L" b a r ", LR"(b"az)"), actual(LR"( foo ," b a r ",b\"az)")); + VERIFY_ARE_EQUAL(expected(LR"(foo, bar)"), actual(LR"("foo, bar")")); + VERIFY_ARE_EQUAL(expected(LR"("foo")", LR"('bar')"), actual(LR"('"foo"', "'bar'")")); + VERIFY_ARE_EQUAL(expected(LR"("foo")", LR"('bar')"), actual(LR"("\"foo\"", '\'bar\'')")); + VERIFY_ARE_EQUAL(expected(L"foo"), actual(LR"(,,,,foo,,,,)")); + } }; diff --git a/tools/Lock-CascadiaFont.ps1 b/tools/Lock-CascadiaFont.ps1 new file mode 100644 index 00000000000..f1a1e384b26 --- /dev/null +++ b/tools/Lock-CascadiaFont.ps1 @@ -0,0 +1,76 @@ +# This script is a failed attempt to lock the Cascadia Mono/Code font files in order to reproduce an issue with the font +# cache service, where it says a font exists, but then fails to use it (see GH#9375). The script doesn't work because +# for some reason DirectWrite is still able to fully use the fonts. It's left here for reference. + +#Requires -RunAsAdministrator + +Add-Type -TypeDefinition @" +using System; +using System.Runtime.InteropServices; + +public class Win32LockFile { + public const uint LOCKFILE_FAIL_IMMEDIATELY = 0x00000001; + public const uint LOCKFILE_EXCLUSIVE_LOCK = 0x00000002; + + [DllImport("kernel32.dll")] + public static extern bool LockFileEx(IntPtr hFile, uint dwFlags, uint dwReserved, uint nNumberOfBytesToLockLow, uint nNumberOfBytesToLockHigh, ref OVERLAPPED lpOverlapped); + + [StructLayout(LayoutKind.Sequential)] + public struct OVERLAPPED { + public uint Internal; + public uint InternalHigh; + public uint Offset; + public uint OffsetHigh; + public IntPtr hEvent; + } +} +"@ + +function Lock-File { + param( + [Parameter(Mandatory=$true)] + [string]$Path + ) + + $file = [System.IO.File]::Open($Path, [System.IO.FileMode]::Open, [System.IO.FileAccess]::Read, [System.IO.FileShare]::ReadWrite) + $overlapped = New-Object Win32LockFile+OVERLAPPED + $result = [Win32LockFile]::LockFileEx( + $file.SafeFileHandle.DangerousGetHandle(), # hFile + [Win32LockFile]::LOCKFILE_EXCLUSIVE_LOCK, # dwFlags + 0, # dwReserved + [UInt32]::MaxValue, # nNumberOfBytesToLockLow + [UInt32]::MaxValue, # nNumberOfBytesToLockHigh + [ref]$overlapped # lpOverlapped + ) + + if (-not $result) { + throw "Failed to lock file" + } + + return $file +} + +$fonts = Get-ItemProperty "HKCU:\Software\Microsoft\Windows NT\CurrentVersion\Fonts\*" + | ForEach-Object { $_.PSobject.Properties } + | Where-Object { $_.Name.StartsWith("Cascadia") } + | ForEach-Object { $_.Value } + +$fonts += @("CascadiaCode.ttf", "CascadiaCodeItalic.ttf", "CascadiaMono.ttf", "CascadiaMonoItalic.ttf") + | ForEach-Object { "C:\Windows\Fonts\$_" } + | Where-Object { Test-Path $_ } + +try { + $handles = $fonts | ForEach-Object { + try { + Lock-File $_ + } + catch { + Write-Error $_ + } + } + Restart-Service FontCache + Write-Host "Press any key to unlock the font files..." + $null = $host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown") +} finally { + $handles | Where-Object { $_ } | ForEach-Object { $_.Close() } +}