mirror of
https://github.com/microsoft/terminal.git
synced 2026-05-21 06:18:34 +00:00
Compare commits
37 Commits
dev/cazamo
...
v1.18.1462
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
14fb08a228 | ||
|
|
d90c837e63 | ||
|
|
910cfe06c4 | ||
|
|
e1e3acf611 | ||
|
|
e4f0ab6c90 | ||
|
|
808d35b815 | ||
|
|
2325442cb8 | ||
|
|
81bf8d5e3b | ||
|
|
9200979cb0 | ||
|
|
89ee00d96e | ||
|
|
ecdba747bb | ||
|
|
068159607e | ||
|
|
a5c351c513 | ||
|
|
02eb1eae59 | ||
|
|
745c4e4f0e | ||
|
|
edb55d74f4 | ||
|
|
521e1318df | ||
|
|
e4a62fb2e1 | ||
|
|
5d1bcd6324 | ||
|
|
6942992d7b | ||
|
|
c7bf735167 | ||
|
|
6e6777a613 | ||
|
|
622844ab3d | ||
|
|
74665fea72 | ||
|
|
0dbcc5bed1 | ||
|
|
747999a7e8 | ||
|
|
11233c5108 | ||
|
|
704be8021c | ||
|
|
9909b890eb | ||
|
|
7e5922f3d3 | ||
|
|
8bc192db65 | ||
|
|
6504066441 | ||
|
|
7c69fe3d0b | ||
|
|
08d395514f | ||
|
|
5c6e626bee | ||
|
|
8d2447bce4 | ||
|
|
e80661f8b1 |
4
.github/actions/spelling/expect/alphabet.txt
vendored
4
.github/actions/spelling/expect/alphabet.txt
vendored
@@ -9,6 +9,7 @@ ABCDEFGHIJ
|
||||
abcdefghijk
|
||||
ABCDEFGHIJKLMNO
|
||||
abcdefghijklmnop
|
||||
ABCDEFGHIJKLMNOPQRS
|
||||
ABCDEFGHIJKLMNOPQRST
|
||||
ABCG
|
||||
ABE
|
||||
@@ -21,9 +22,12 @@ BBGGRR
|
||||
efg
|
||||
EFG
|
||||
EFGh
|
||||
KLMNOQQQQQQQQQQ
|
||||
QQQQQQQQQQABCDEFGHIJ
|
||||
QQQQQQQQQQABCDEFGHIJKLMNOPQRS
|
||||
QQQQQQQQQQABCDEFGHIJKLMNOPQRSTQQQQQQQQQ
|
||||
QQQQQQQQQQABCDEFGHIJKLMNOPQRSTQQQQQQQQQQ
|
||||
QQQQQQQQQQABCDEFGHIJPQRST
|
||||
QQQQQQQQQQABCDEFGHIJPQRSTQQQQQQQQQQ
|
||||
qrstuvwxyz
|
||||
qwerty
|
||||
|
||||
@@ -58,4 +58,7 @@ Copy-Item "build\Helix\EnsureMachineState.ps1" "$payloadDir"
|
||||
# Extract the unpackaged distribution of Windows Terminal to the payload directory,
|
||||
# where it will create a subdirectory named terminal-0.0.1.0
|
||||
# This is referenced in TerminalApp.cs later as part of the test harness.
|
||||
& tar -x -v -f "$repoDirectory\Artifacts\$ArtifactName\unpackaged\WindowsTerminalDev_0.0.1.0_x64.zip" -C "$payloadDir"
|
||||
## 1.18 HAX: Preview Build
|
||||
#& tar -x -v -f "$repoDirectory\Artifacts\$ArtifactName\unpackaged\Microsoft.WindowsTerminal_1.0.0.0_x64.zip" -C "$payloadDir"
|
||||
& tar -x -v -f "$repoDirectory\Artifacts\$ArtifactName\unpackaged\Microsoft.WindowsTerminalPreview_0.5.0.0_x64.zip" -C "$payloadDir"
|
||||
Move-Item "$payloadDir\terminal-*" "$payloadDir\terminal-0.0.1.0"
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<!-- Optional, defaults to main. Name of the branch which will be used for calculating branch point. -->
|
||||
<PGOBranch>main</PGOBranch>
|
||||
<PGOBranch>release-1.18</PGOBranch>
|
||||
|
||||
<!-- Mandatory. Name of the NuGet package which will contain PGO databases for consumption by build system. -->
|
||||
<PGOPackageName>Microsoft.Internal.Windows.Terminal.PGODatabase</PGOPackageName>
|
||||
|
||||
@@ -18,6 +18,7 @@ stages:
|
||||
- template: ./templates/build-console-pgo.yml
|
||||
parameters:
|
||||
platform: x64
|
||||
additionalBuildArguments: "/p:WindowsTerminalOfficialBuild=true /p:WindowsTerminalBranding=Preview"
|
||||
- stage: Publish_PGO_Databases
|
||||
displayName: Publish PGO databases
|
||||
dependsOn: ['Build_x64']
|
||||
|
||||
@@ -10,7 +10,7 @@ namespace Microsoft.Terminal.Remoting
|
||||
CommandlineArgs(String[] args, String cwd, UInt32 showWindowCommand);
|
||||
|
||||
String[] Commandline { get; set; };
|
||||
String CurrentDirectory();
|
||||
String CurrentDirectory { get; };
|
||||
UInt32 ShowWindowCommand { get; };
|
||||
};
|
||||
|
||||
|
||||
@@ -41,9 +41,8 @@ namespace
|
||||
// Make a temporary monarch CLSID based on the unpackaged install root
|
||||
std::filesystem::path modulePath{ wil::GetModuleFileNameW<std::wstring>(wil::GetModuleInstanceHandle()) };
|
||||
modulePath.remove_filename();
|
||||
std::wstring pathRootAsString{ modulePath.wstring() };
|
||||
|
||||
return Utils::CreateV5Uuid(processRootHashedGuidBase, std::as_bytes(std::span{ pathRootAsString }));
|
||||
return Utils::CreateV5Uuid(processRootHashedGuidBase, std::as_bytes(std::span{ modulePath.native() }));
|
||||
}();
|
||||
return processRootHashedGuid;
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@
|
||||
Color="{ThemeResource SystemErrorTextColor}" />
|
||||
|
||||
<!-- Suppress top padding -->
|
||||
<Thickness x:Key="TabViewHeaderPadding">9,0,5,0</Thickness>
|
||||
<Thickness x:Key="TabViewHeaderPadding">0,0,0,0</Thickness>
|
||||
|
||||
<Thickness x:Key="TabViewItemBorderThickness">1,1,1,0</Thickness>
|
||||
|
||||
|
||||
@@ -146,7 +146,7 @@
|
||||
-->
|
||||
<x:Double x:Key="CaptionButtonHeightWindowed">40.0</x:Double>
|
||||
<!-- 32 + (1 to compensate for GH#10746) + (-1 for GH#15164) -->
|
||||
<x:Double x:Key="CaptionButtonHeightMaximized">33.0</x:Double>
|
||||
<x:Double x:Key="CaptionButtonHeightMaximized">32.0</x:Double>
|
||||
|
||||
<Style x:Key="CaptionButton"
|
||||
TargetType="Button">
|
||||
|
||||
@@ -1121,19 +1121,15 @@ winrt::fire_and_forget Pane::_playBellSound(winrt::Windows::Foundation::Uri uri)
|
||||
co_await wil::resume_foreground(_root.Dispatcher());
|
||||
if (auto pane{ weakThis.lock() })
|
||||
{
|
||||
// BODGY
|
||||
// GH#12258: We learned that if you leave the MediaPlayer open, and
|
||||
// press the media keys (like play/pause), then the OS will _replay the
|
||||
// bell_. So we have to re-create the MediaPlayer each time we want to
|
||||
// play the bell, to make sure a subsequent play doesn't come through
|
||||
// and reactivate the old one.
|
||||
|
||||
if (!_bellPlayer)
|
||||
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();
|
||||
}
|
||||
@@ -1143,27 +1139,6 @@ winrt::fire_and_forget Pane::_playBellSound(winrt::Windows::Foundation::Uri uri)
|
||||
const auto item{ winrt::Windows::Media::Playback::MediaPlaybackItem(source) };
|
||||
_bellPlayer.Source(item);
|
||||
_bellPlayer.Play();
|
||||
|
||||
// This lambda will clean up the bell player when we're done with it.
|
||||
auto weakThis2{ weak_from_this() };
|
||||
_mediaEndedRevoker = _bellPlayer.MediaEnded(winrt::auto_revoke, [weakThis2](auto&&, auto&&) {
|
||||
if (auto self{ weakThis2.lock() })
|
||||
{
|
||||
if (self->_bellPlayer)
|
||||
{
|
||||
// We need to make sure clear out the current track
|
||||
// that's being played, again, so that the system can't
|
||||
// come through and replay it. In testing, we needed to
|
||||
// do this, closing the MediaPlayer alone wasn't good
|
||||
// enough.
|
||||
self->_bellPlayer.Pause();
|
||||
self->_bellPlayer.Source(nullptr);
|
||||
self->_bellPlayer.Close();
|
||||
}
|
||||
self->_mediaEndedRevoker.revoke();
|
||||
self->_bellPlayer = nullptr;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1269,14 +1244,14 @@ 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.
|
||||
_mediaEndedRevoker.revoke();
|
||||
if (_bellPlayer)
|
||||
{
|
||||
_bellPlayer.Pause();
|
||||
_bellPlayer.Source(nullptr);
|
||||
_bellPlayer.Close();
|
||||
_bellPlayer = nullptr;
|
||||
_bellPlayerCreated = false;
|
||||
}
|
||||
_bellPlayer = nullptr;
|
||||
|
||||
if (_IsLeaf())
|
||||
{
|
||||
|
||||
@@ -265,7 +265,7 @@ private:
|
||||
bool _zoomed{ false };
|
||||
|
||||
winrt::Windows::Media::Playback::MediaPlayer _bellPlayer{ nullptr };
|
||||
winrt::Windows::Media::Playback::MediaPlayer::MediaEnded_revoker _mediaEndedRevoker;
|
||||
bool _bellPlayerCreated{ false };
|
||||
|
||||
bool _IsLeaf() const noexcept;
|
||||
bool _HasFocusedChild() const noexcept;
|
||||
|
||||
@@ -238,12 +238,7 @@ namespace winrt::TerminalApp::implementation
|
||||
page->_OpenNewTerminalViaDropdown(NewTerminalArgs());
|
||||
}
|
||||
});
|
||||
_newTabButton.Drop([weakThis{ get_weak() }](const Windows::Foundation::IInspectable&, winrt::Windows::UI::Xaml::DragEventArgs e) {
|
||||
if (auto page{ weakThis.get() })
|
||||
{
|
||||
page->NewTerminalByDrop(e);
|
||||
}
|
||||
});
|
||||
_newTabButton.Drop({ get_weak(), &TerminalPage::_NewTerminalByDrop });
|
||||
_tabView.SelectionChanged({ this, &TerminalPage::_OnTabSelectionChanged });
|
||||
_tabView.TabCloseRequested({ this, &TerminalPage::_OnTabCloseRequested });
|
||||
_tabView.TabItemsChanged({ this, &TerminalPage::_OnTabItemsChanged });
|
||||
@@ -402,35 +397,46 @@ namespace winrt::TerminalApp::implementation
|
||||
}
|
||||
}
|
||||
|
||||
winrt::fire_and_forget TerminalPage::NewTerminalByDrop(winrt::Windows::UI::Xaml::DragEventArgs& e)
|
||||
winrt::fire_and_forget TerminalPage::_NewTerminalByDrop(const Windows::Foundation::IInspectable&, winrt::Windows::UI::Xaml::DragEventArgs e)
|
||||
try
|
||||
{
|
||||
Windows::Foundation::Collections::IVectorView<Windows::Storage::IStorageItem> items;
|
||||
try
|
||||
const auto data = e.DataView();
|
||||
if (!data.Contains(StandardDataFormats::StorageItems()))
|
||||
{
|
||||
items = co_await e.DataView().GetStorageItemsAsync();
|
||||
co_return;
|
||||
}
|
||||
CATCH_LOG();
|
||||
|
||||
if (items.Size() == 1)
|
||||
const auto weakThis = get_weak();
|
||||
const auto items = co_await data.GetStorageItemsAsync();
|
||||
const auto strongThis = weakThis.get();
|
||||
if (!strongThis)
|
||||
{
|
||||
std::filesystem::path path(items.GetAt(0).Path().c_str());
|
||||
co_return;
|
||||
}
|
||||
|
||||
TraceLoggingWrite(
|
||||
g_hTerminalAppProvider,
|
||||
"NewTabByDragDrop",
|
||||
TraceLoggingDescription("Event emitted when the user drag&drops onto the new tab button"),
|
||||
TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES),
|
||||
TelemetryPrivacyDataTag(PDT_ProductAndServiceUsage));
|
||||
|
||||
for (const auto& item : items)
|
||||
{
|
||||
auto directory = item.Path();
|
||||
|
||||
std::filesystem::path path(std::wstring_view{ directory });
|
||||
if (!std::filesystem::is_directory(path))
|
||||
{
|
||||
path = path.parent_path();
|
||||
directory = winrt::hstring{ path.parent_path().native() };
|
||||
}
|
||||
|
||||
NewTerminalArgs args;
|
||||
args.StartingDirectory(winrt::hstring{ path.wstring() });
|
||||
this->_OpenNewTerminalViaDropdown(args);
|
||||
|
||||
TraceLoggingWrite(
|
||||
g_hTerminalAppProvider,
|
||||
"NewTabByDragDrop",
|
||||
TraceLoggingDescription("Event emitted when the user drag&drops onto the new tab button"),
|
||||
TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES),
|
||||
TelemetryPrivacyDataTag(PDT_ProductAndServiceUsage));
|
||||
args.StartingDirectory(directory);
|
||||
_OpenNewTerminalViaDropdown(args);
|
||||
}
|
||||
}
|
||||
CATCH_LOG()
|
||||
|
||||
// Method Description:
|
||||
// - This method is called once command palette action was chosen for dispatching
|
||||
@@ -551,27 +557,28 @@ namespace winrt::TerminalApp::implementation
|
||||
// Handle it on a subsequent pass of the UI thread.
|
||||
co_await wil::resume_foreground(Dispatcher(), CoreDispatcherPriority::Normal);
|
||||
|
||||
// If the caller provided a CWD, switch to that directory, then switch
|
||||
// If the caller provided a CWD, "switch" to that directory, then switch
|
||||
// back once we're done. This looks weird though, because we have to set
|
||||
// up the scope_exit _first_. We'll release the scope_exit if we don't
|
||||
// actually need it.
|
||||
auto originalCwd{ wil::GetCurrentDirectoryW<std::wstring>() };
|
||||
auto restoreCwd = wil::scope_exit([&originalCwd]() {
|
||||
|
||||
auto originalVirtualCwd{ _WindowProperties.VirtualWorkingDirectory() };
|
||||
auto restoreCwd = wil::scope_exit([&originalVirtualCwd, this]() {
|
||||
// ignore errors, we'll just power on through. We'd rather do
|
||||
// something rather than fail silently if the directory doesn't
|
||||
// actually exist.
|
||||
LOG_IF_WIN32_BOOL_FALSE(SetCurrentDirectory(originalCwd.c_str()));
|
||||
_WindowProperties.VirtualWorkingDirectory(originalVirtualCwd);
|
||||
});
|
||||
|
||||
if (cwd.empty())
|
||||
{
|
||||
// We didn't actually need to change the virtual CWD, so we don't
|
||||
// need to restore it
|
||||
restoreCwd.release();
|
||||
}
|
||||
else
|
||||
{
|
||||
// ignore errors, we'll just power on through. We'd rather do
|
||||
// something rather than fail silently if the directory doesn't
|
||||
// actually exist.
|
||||
LOG_IF_WIN32_BOOL_FALSE(SetCurrentDirectory(cwd.c_str()));
|
||||
_WindowProperties.VirtualWorkingDirectory(cwd);
|
||||
}
|
||||
|
||||
if (auto page{ weakThis.get() })
|
||||
@@ -851,7 +858,10 @@ namespace winrt::TerminalApp::implementation
|
||||
});
|
||||
// Necessary for fly-out sub items to get focus on a tab before collapsing. Related to #15049
|
||||
newTabFlyout.Closing([this](auto&&, auto&&) {
|
||||
_FocusCurrentTab(true);
|
||||
if (!_commandPaletteIs(Visibility::Visible))
|
||||
{
|
||||
_FocusCurrentTab(true);
|
||||
}
|
||||
});
|
||||
_newTabButton.Flyout(newTabFlyout);
|
||||
}
|
||||
@@ -1113,6 +1123,7 @@ namespace winrt::TerminalApp::implementation
|
||||
if (profile)
|
||||
{
|
||||
newTerminalArgs.Profile(::Microsoft::Console::Utils::GuidToString(profile.Guid()));
|
||||
newTerminalArgs.StartingDirectory(_evaluatePathForCwd(profile.EvaluatedStartingDirectory()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1158,6 +1169,11 @@ namespace winrt::TerminalApp::implementation
|
||||
}
|
||||
}
|
||||
|
||||
std::wstring TerminalPage::_evaluatePathForCwd(const std::wstring_view path)
|
||||
{
|
||||
return Utils::EvaluateStartingDirectory(_WindowProperties.VirtualWorkingDirectory(), path);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Creates a new connection based on the profile settings
|
||||
// Arguments:
|
||||
@@ -1188,7 +1204,7 @@ namespace winrt::TerminalApp::implementation
|
||||
connection = TerminalConnection::ConptyConnection{};
|
||||
}
|
||||
|
||||
auto valueSet = TerminalConnection::ConptyConnection::CreateSettings(azBridgePath.wstring(),
|
||||
auto valueSet = TerminalConnection::ConptyConnection::CreateSettings(azBridgePath.native(),
|
||||
L".",
|
||||
L"Azure",
|
||||
nullptr,
|
||||
@@ -1226,16 +1242,7 @@ namespace winrt::TerminalApp::implementation
|
||||
// construction, because the connection might not spawn the child
|
||||
// process until later, on another thread, after we've already
|
||||
// restored the CWD to its original value.
|
||||
auto newWorkingDirectory{ settings.StartingDirectory() };
|
||||
if (newWorkingDirectory.size() == 0 || newWorkingDirectory.size() == 1 &&
|
||||
!(newWorkingDirectory[0] == L'~' || newWorkingDirectory[0] == L'/'))
|
||||
{ // We only want to resolve the new WD against the CWD if it doesn't look like a Linux path (see GH#592)
|
||||
auto cwdString{ wil::GetCurrentDirectoryW<std::wstring>() };
|
||||
std::filesystem::path cwd{ cwdString };
|
||||
cwd /= settings.StartingDirectory().c_str();
|
||||
newWorkingDirectory = winrt::hstring{ cwd.wstring() };
|
||||
}
|
||||
|
||||
auto newWorkingDirectory{ _evaluatePathForCwd(settings.StartingDirectory()) };
|
||||
auto conhostConn = TerminalConnection::ConptyConnection();
|
||||
auto valueSet = TerminalConnection::ConptyConnection::CreateSettings(settings.Commandline(),
|
||||
newWorkingDirectory,
|
||||
@@ -1444,9 +1451,10 @@ namespace winrt::TerminalApp::implementation
|
||||
return;
|
||||
}
|
||||
|
||||
if (const auto p = CommandPaletteElement(); p && p.Visibility() == Visibility::Visible && cmd.ActionAndArgs().Action() != ShortcutAction::ToggleCommandPalette)
|
||||
if (_commandPaletteIs(Visibility::Visible) &&
|
||||
cmd.ActionAndArgs().Action() != ShortcutAction::ToggleCommandPalette)
|
||||
{
|
||||
p.Visibility(Visibility::Collapsed);
|
||||
CommandPaletteElement().Visibility(Visibility::Collapsed);
|
||||
}
|
||||
|
||||
// Let's assume the user has bound the dead key "^" to a sendInput command that sends "b".
|
||||
@@ -1778,6 +1786,11 @@ namespace winrt::TerminalApp::implementation
|
||||
|
||||
return _loadCommandPaletteSlowPath();
|
||||
}
|
||||
bool TerminalPage::_commandPaletteIs(WUX::Visibility visibility)
|
||||
{
|
||||
const auto p = CommandPaletteElement();
|
||||
return p && p.Visibility() == visibility;
|
||||
}
|
||||
|
||||
CommandPalette TerminalPage::_loadCommandPaletteSlowPath()
|
||||
{
|
||||
@@ -1789,7 +1802,7 @@ namespace winrt::TerminalApp::implementation
|
||||
// When the visibility of the command palette changes to "collapsed",
|
||||
// the palette has been closed. Toss focus back to the currently active control.
|
||||
p.RegisterPropertyChangedCallback(UIElement::VisibilityProperty(), [this](auto&&, auto&&) {
|
||||
if (CommandPaletteElement().Visibility() == Visibility::Collapsed)
|
||||
if (_commandPaletteIs(Visibility::Collapsed))
|
||||
{
|
||||
_FocusActiveControl(nullptr, nullptr);
|
||||
}
|
||||
@@ -2077,6 +2090,13 @@ namespace winrt::TerminalApp::implementation
|
||||
const auto windowId{ args.Window() };
|
||||
if (!windowId.empty())
|
||||
{
|
||||
// if the windowId is the same as our name, do nothing
|
||||
if (windowId == WindowProperties().WindowName() ||
|
||||
windowId == winrt::to_hstring(WindowProperties().WindowId()))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (const auto terminalTab{ _GetFocusedTabImpl() })
|
||||
{
|
||||
auto startupActions = terminalTab->BuildStartupActions(true);
|
||||
@@ -2756,9 +2776,8 @@ namespace winrt::TerminalApp::implementation
|
||||
// Arguments:
|
||||
// - sender (not used)
|
||||
// - args: the arguments specifying how to set the display status to ShowWindow for our window handle
|
||||
winrt::fire_and_forget TerminalPage::_ShowWindowChangedHandler(const IInspectable /*sender*/, const Microsoft::Terminal::Control::ShowWindowArgs args)
|
||||
void TerminalPage::_ShowWindowChangedHandler(const IInspectable& /*sender*/, const Microsoft::Terminal::Control::ShowWindowArgs args)
|
||||
{
|
||||
co_await resume_foreground(Dispatcher());
|
||||
_ShowWindowChangedHandlers(*this, args);
|
||||
}
|
||||
|
||||
@@ -4236,6 +4255,9 @@ namespace winrt::TerminalApp::implementation
|
||||
// whatever the default profile's GUID is.
|
||||
|
||||
newTerminalArgs.Profile(::Microsoft::Console::Utils::GuidToString(profile.Guid()));
|
||||
|
||||
newTerminalArgs.StartingDirectory(_evaluatePathForCwd(controlSettings.DefaultSettings().StartingDirectory()));
|
||||
|
||||
_OpenElevatedWT(newTerminalArgs);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -106,8 +106,6 @@ namespace winrt::TerminalApp::implementation
|
||||
void HandoffToElevated(const Microsoft::Terminal::Settings::Model::CascadiaSettings& settings);
|
||||
Microsoft::Terminal::Settings::Model::WindowLayout GetWindowLayout();
|
||||
|
||||
winrt::fire_and_forget NewTerminalByDrop(winrt::Windows::UI::Xaml::DragEventArgs& e);
|
||||
|
||||
hstring Title();
|
||||
|
||||
void TitlebarClicked();
|
||||
@@ -275,7 +273,11 @@ namespace winrt::TerminalApp::implementation
|
||||
|
||||
winrt::Microsoft::Terminal::TerminalConnection::ConptyConnection::NewConnection_revoker _newConnectionRevoker;
|
||||
|
||||
winrt::fire_and_forget _NewTerminalByDrop(const Windows::Foundation::IInspectable&, winrt::Windows::UI::Xaml::DragEventArgs e);
|
||||
|
||||
__declspec(noinline) CommandPalette _loadCommandPaletteSlowPath();
|
||||
bool _commandPaletteIs(winrt::Windows::UI::Xaml::Visibility visibility);
|
||||
|
||||
winrt::Windows::Foundation::IAsyncOperation<winrt::Windows::UI::Xaml::Controls::ContentDialogResult> _ShowDialogHelper(const std::wstring_view& name);
|
||||
|
||||
void _ShowAboutDialog();
|
||||
@@ -293,6 +295,9 @@ namespace winrt::TerminalApp::implementation
|
||||
void _OpenNewTabDropdown();
|
||||
HRESULT _OpenNewTab(const Microsoft::Terminal::Settings::Model::NewTerminalArgs& newTerminalArgs, winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection existingConnection = nullptr);
|
||||
void _CreateNewTabFromPane(std::shared_ptr<Pane> 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> pane);
|
||||
void _restartPaneConnection(const std::shared_ptr<Pane>& pane);
|
||||
@@ -505,7 +510,7 @@ namespace winrt::TerminalApp::implementation
|
||||
void _updateAllTabCloseButtons(const winrt::TerminalApp::TabBase& focusedTab);
|
||||
void _updatePaneResources(const winrt::Windows::UI::Xaml::ElementTheme& requestedTheme);
|
||||
|
||||
winrt::fire_and_forget _ShowWindowChangedHandler(const IInspectable sender, const winrt::Microsoft::Terminal::Control::ShowWindowArgs args);
|
||||
void _ShowWindowChangedHandler(const IInspectable& sender, const winrt::Microsoft::Terminal::Control::ShowWindowArgs args);
|
||||
winrt::fire_and_forget _windowPropertyChanged(const IInspectable& sender, const winrt::Windows::UI::Xaml::Data::PropertyChangedEventArgs& args);
|
||||
|
||||
void _onTabDragStarting(const winrt::Microsoft::UI::Xaml::Controls::TabView& sender, const winrt::Microsoft::UI::Xaml::Controls::TabViewTabDragStartingEventArgs& e);
|
||||
|
||||
@@ -51,6 +51,8 @@ namespace TerminalApp
|
||||
String WindowNameForDisplay { get; };
|
||||
String WindowIdForDisplay { get; };
|
||||
|
||||
String VirtualWorkingDirectory { get; set; };
|
||||
|
||||
Boolean IsQuakeWindow();
|
||||
};
|
||||
|
||||
|
||||
@@ -950,7 +950,7 @@ namespace winrt::TerminalApp::implementation
|
||||
}
|
||||
});
|
||||
|
||||
events.focusToken = control.FocusFollowMouseRequested([dispatcher, weakThis](auto&& sender, auto&&) -> winrt::fire_and_forget {
|
||||
events.focusToken = control.FocusFollowMouseRequested([dispatcher, weakThis](auto sender, auto) -> winrt::fire_and_forget {
|
||||
co_await wil::resume_foreground(dispatcher);
|
||||
if (const auto tab{ weakThis.get() })
|
||||
{
|
||||
|
||||
@@ -1019,11 +1019,14 @@ namespace winrt::TerminalApp::implementation
|
||||
// returned.
|
||||
// Arguments:
|
||||
// - args: an array of strings to process as a commandline. These args can contain spaces
|
||||
// - cwd: The CWD that this window should treat as its own "virtual" CWD
|
||||
// Return Value:
|
||||
// - the result of the first command who's parsing returned a non-zero code,
|
||||
// or 0. (see TerminalWindow::_ParseArgs)
|
||||
int32_t TerminalWindow::SetStartupCommandline(array_view<const winrt::hstring> args)
|
||||
int32_t TerminalWindow::SetStartupCommandline(array_view<const winrt::hstring> args, winrt::hstring cwd)
|
||||
{
|
||||
_WindowProperties->SetInitialCwd(std::move(cwd));
|
||||
|
||||
// This is called in AppHost::ctor(), before we've created the window
|
||||
// (or called TerminalWindow::Initialize)
|
||||
const auto result = _appArgs.ParseArgs(args);
|
||||
@@ -1345,6 +1348,7 @@ namespace winrt::TerminalApp::implementation
|
||||
CATCH_LOG();
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t WindowProperties::WindowId() const noexcept
|
||||
{
|
||||
return _WindowId;
|
||||
|
||||
@@ -48,8 +48,14 @@ namespace winrt::TerminalApp::implementation
|
||||
winrt::hstring WindowNameForDisplay() const noexcept;
|
||||
bool IsQuakeWindow() const noexcept;
|
||||
|
||||
WINRT_OBSERVABLE_PROPERTY(winrt::hstring, VirtualWorkingDirectory, _PropertyChangedHandlers, L"");
|
||||
|
||||
WINRT_CALLBACK(PropertyChanged, Windows::UI::Xaml::Data::PropertyChangedEventHandler);
|
||||
|
||||
public:
|
||||
// Used for setting the initial CWD, before we have XAML set up for property change notifications.
|
||||
void SetInitialCwd(winrt::hstring cwd) { _VirtualWorkingDirectory = std::move(cwd); };
|
||||
|
||||
private:
|
||||
winrt::hstring _WindowName{};
|
||||
uint64_t _WindowId{ 0 };
|
||||
@@ -71,7 +77,7 @@ namespace winrt::TerminalApp::implementation
|
||||
|
||||
bool HasCommandlineArguments() const noexcept;
|
||||
|
||||
int32_t SetStartupCommandline(array_view<const winrt::hstring> actions);
|
||||
int32_t SetStartupCommandline(array_view<const winrt::hstring> actions, winrt::hstring cwd);
|
||||
void SetStartupContent(const winrt::hstring& content, const Windows::Foundation::IReference<Windows::Foundation::Rect>& contentBounds);
|
||||
int32_t ExecuteCommandline(array_view<const winrt::hstring> actions, const winrt::hstring& cwd);
|
||||
void SetSettingsStartupArgs(const std::vector<winrt::Microsoft::Terminal::Settings::Model::ActionAndArgs>& actions);
|
||||
|
||||
@@ -53,7 +53,7 @@ namespace TerminalApp
|
||||
|
||||
Boolean HasCommandlineArguments();
|
||||
|
||||
Int32 SetStartupCommandline(String[] commands);
|
||||
Int32 SetStartupCommandline(String[] commands, String cwd);
|
||||
void SetStartupContent(String json, Windows.Foundation.IReference<Windows.Foundation.Rect> bounds);
|
||||
Int32 ExecuteCommandline(String[] commands, String cwd);
|
||||
String ParseCommandlineMessage { get; };
|
||||
|
||||
@@ -82,9 +82,10 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
_actualFont{ DEFAULT_FONT_FACE, 0, DEFAULT_FONT_WEIGHT, { 0, DEFAULT_FONT_SIZE }, CP_UTF8, false }
|
||||
{
|
||||
_settings = winrt::make_self<implementation::ControlSettings>(settings, unfocusedAppearance);
|
||||
|
||||
_terminal = std::make_shared<::Microsoft::Terminal::Core::Terminal>();
|
||||
|
||||
_setupDispatcherAndCallbacks();
|
||||
|
||||
Connection(connection);
|
||||
|
||||
_terminal->SetWriteInputCallback([this](std::wstring_view wstr) {
|
||||
@@ -137,17 +138,10 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
|
||||
_renderer->SetBackgroundColorChangedCallback([this]() { _rendererBackgroundColorChanged(); });
|
||||
_renderer->SetFrameColorChangedCallback([this]() { _rendererTabColorChanged(); });
|
||||
|
||||
_renderer->SetRendererEnteredErrorStateCallback([weakThis = get_weak()]() {
|
||||
if (auto strongThis{ weakThis.get() })
|
||||
{
|
||||
strongThis->_RendererEnteredErrorStateHandlers(*strongThis, nullptr);
|
||||
}
|
||||
});
|
||||
_renderer->SetRendererEnteredErrorStateCallback([this]() { _RendererEnteredErrorStateHandlers(nullptr, nullptr); });
|
||||
|
||||
THROW_IF_FAILED(localPointerToThread->Initialize(_renderer.get()));
|
||||
}
|
||||
_setupDispatcherAndCallbacks();
|
||||
|
||||
UpdateSettings(settings, unfocusedAppearance);
|
||||
}
|
||||
@@ -179,7 +173,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
// need to hop across the process boundary every time text is output.
|
||||
// We can throttle this to once every 8ms, which will get us out of
|
||||
// the way of the main output & rendering threads.
|
||||
_tsfTryRedrawCanvas = std::make_shared<ThrottledFuncTrailing<>>(
|
||||
const auto shared = _shared.lock();
|
||||
shared->tsfTryRedrawCanvas = std::make_shared<ThrottledFuncTrailing<>>(
|
||||
_dispatcher,
|
||||
TsfRedrawInterval,
|
||||
[weakThis = get_weak()]() {
|
||||
@@ -191,7 +186,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
|
||||
// NOTE: Calling UpdatePatternLocations from a background
|
||||
// thread is a workaround for us to hit GH#12607 less often.
|
||||
_updatePatternLocations = std::make_unique<til::throttled_func_trailing<>>(
|
||||
shared->updatePatternLocations = std::make_unique<til::throttled_func_trailing<>>(
|
||||
UpdatePatternLocationsInterval,
|
||||
[weakTerminal = std::weak_ptr{ _terminal }]() {
|
||||
if (const auto t = weakTerminal.lock())
|
||||
@@ -201,7 +196,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
}
|
||||
});
|
||||
|
||||
_updateScrollBar = std::make_shared<ThrottledFuncTrailing<Control::ScrollPositionChangedArgs>>(
|
||||
shared->updateScrollBar = std::make_shared<ThrottledFuncTrailing<Control::ScrollPositionChangedArgs>>(
|
||||
_dispatcher,
|
||||
ScrollBarUpdateInterval,
|
||||
[weakThis = get_weak()](const auto& update) {
|
||||
@@ -231,9 +226,10 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
// Clear out any throttled funcs that we had wired up to run on this UI
|
||||
// thread. These will be recreated in _setupDispatcherAndCallbacks, when
|
||||
// we're re-attached to a new control (on a possibly new UI thread).
|
||||
_tsfTryRedrawCanvas.reset();
|
||||
_updatePatternLocations.reset();
|
||||
_updateScrollBar.reset();
|
||||
const auto shared = _shared.lock();
|
||||
shared->tsfTryRedrawCanvas.reset();
|
||||
shared->updatePatternLocations.reset();
|
||||
shared->updateScrollBar.reset();
|
||||
}
|
||||
|
||||
void ControlCore::AttachToNewControl(const Microsoft::Terminal::Control::IKeyBindings& keyBindings)
|
||||
@@ -318,7 +314,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
{ // scope for terminalLock
|
||||
auto terminalLock = _terminal->LockForWriting();
|
||||
|
||||
if (_initializedTerminal)
|
||||
if (_initializedTerminal.load(std::memory_order_relaxed))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -403,7 +399,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
|
||||
THROW_IF_FAILED(_renderEngine->Enable());
|
||||
|
||||
_initializedTerminal = true;
|
||||
_initializedTerminal.store(true, std::memory_order_relaxed);
|
||||
} // scope for TerminalLock
|
||||
|
||||
// Start the connection outside of lock, because it could
|
||||
@@ -423,7 +419,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
// - <none>
|
||||
void ControlCore::EnablePainting()
|
||||
{
|
||||
if (_initializedTerminal)
|
||||
if (_initializedTerminal.load(std::memory_order_relaxed))
|
||||
{
|
||||
_renderer->EnablePainting();
|
||||
}
|
||||
@@ -656,9 +652,10 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
// itself - it was initiated by the mouse wheel, or the scrollbar.
|
||||
_terminal->UserScrollViewport(viewTop);
|
||||
|
||||
if (_updatePatternLocations)
|
||||
const auto shared = _shared.lock_shared();
|
||||
if (shared->updatePatternLocations)
|
||||
{
|
||||
(*_updatePatternLocations)();
|
||||
(*shared->updatePatternLocations)();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -825,7 +822,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
// Update the terminal core with its new Core settings
|
||||
_terminal->UpdateSettings(*_settings);
|
||||
|
||||
if (!_initializedTerminal)
|
||||
if (!_initializedTerminal.load(std::memory_order_relaxed))
|
||||
{
|
||||
// If we haven't initialized, there's no point in continuing.
|
||||
// Initialization will handle the renderer settings.
|
||||
@@ -1434,10 +1431,11 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
const int viewHeight,
|
||||
const int bufferSize)
|
||||
{
|
||||
if (!_initializedTerminal)
|
||||
if (!_initializedTerminal.load(std::memory_order_relaxed))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Clear the regex pattern tree so the renderer does not try to render them while scrolling
|
||||
// We're **NOT** taking the lock here unlike _scrollbarChangeHandler because
|
||||
// we are already under lock (since this usually happens as a result of writing).
|
||||
@@ -1448,20 +1446,18 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
auto update{ winrt::make<ScrollPositionChangedArgs>(viewTop,
|
||||
viewHeight,
|
||||
bufferSize) };
|
||||
if (!_inUnitTests && _updateScrollBar)
|
||||
{
|
||||
_updateScrollBar->Run(update);
|
||||
}
|
||||
else
|
||||
|
||||
if (_inUnitTests) [[unlikely]]
|
||||
{
|
||||
_ScrollPositionChangedHandlers(*this, update);
|
||||
}
|
||||
|
||||
// Additionally, start the throttled update of where our links are.
|
||||
|
||||
if (_updatePatternLocations)
|
||||
else
|
||||
{
|
||||
(*_updatePatternLocations)();
|
||||
const auto shared = _shared.lock_shared();
|
||||
if (shared->updateScrollBar)
|
||||
{
|
||||
shared->updateScrollBar->Run(update);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1469,9 +1465,10 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
{
|
||||
// When the buffer's cursor moves, start the throttled func to
|
||||
// eventually dispatch a CursorPositionChanged event.
|
||||
if (_tsfTryRedrawCanvas)
|
||||
const auto shared = _shared.lock_shared();
|
||||
if (shared->tsfTryRedrawCanvas)
|
||||
{
|
||||
_tsfTryRedrawCanvas->Run();
|
||||
shared->tsfTryRedrawCanvas->Run();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1482,11 +1479,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
|
||||
void ControlCore::_terminalShowWindowChanged(bool showOrHide)
|
||||
{
|
||||
if (_initializedTerminal)
|
||||
{
|
||||
auto showWindow = winrt::make_self<implementation::ShowWindowArgs>(showOrHide);
|
||||
_ShowWindowChangedHandlers(*this, *showWindow);
|
||||
}
|
||||
auto showWindow = winrt::make_self<implementation::ShowWindowArgs>(showOrHide);
|
||||
_ShowWindowChangedHandlers(*this, *showWindow);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
@@ -1617,6 +1611,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
|
||||
const auto weakThis{ get_weak() };
|
||||
|
||||
// Concurrent read of _dispatcher is safe, because Detach() calls WaitForPaintCompletionAndDisable()
|
||||
// which blocks until this call returns. _dispatcher will only be changed afterwards.
|
||||
co_await wil::resume_foreground(_dispatcher);
|
||||
|
||||
if (auto core{ weakThis.get() })
|
||||
@@ -1686,7 +1682,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
Core::Point ControlCore::CursorPosition() const
|
||||
{
|
||||
// If we haven't been initialized yet, then fake it.
|
||||
if (!_initializedTerminal)
|
||||
if (!_initializedTerminal.load(std::memory_order_relaxed))
|
||||
{
|
||||
return { 0, 0 };
|
||||
}
|
||||
@@ -1805,9 +1801,10 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
_terminal->Write(hstr);
|
||||
|
||||
// Start the throttled update of where our hyperlinks are.
|
||||
if (_updatePatternLocations)
|
||||
const auto shared = _shared.lock_shared();
|
||||
if (shared->updatePatternLocations)
|
||||
{
|
||||
(*_updatePatternLocations)();
|
||||
(*shared->updatePatternLocations)();
|
||||
}
|
||||
}
|
||||
catch (...)
|
||||
@@ -2016,7 +2013,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
// - <none>
|
||||
void ControlCore::WindowVisibilityChanged(const bool showOrHide)
|
||||
{
|
||||
if (_initializedTerminal)
|
||||
if (_initializedTerminal.load(std::memory_order_relaxed))
|
||||
{
|
||||
// show is true, hide is false
|
||||
if (auto conpty{ _connection.try_as<TerminalConnection::ConptyConnection>() })
|
||||
|
||||
@@ -266,7 +266,14 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
// clang-format on
|
||||
|
||||
private:
|
||||
bool _initializedTerminal{ false };
|
||||
struct SharedState
|
||||
{
|
||||
std::shared_ptr<ThrottledFuncTrailing<>> tsfTryRedrawCanvas;
|
||||
std::unique_ptr<til::throttled_func_trailing<>> updatePatternLocations;
|
||||
std::shared_ptr<ThrottledFuncTrailing<Control::ScrollPositionChangedArgs>> updateScrollBar;
|
||||
};
|
||||
|
||||
std::atomic<bool> _initializedTerminal{ false };
|
||||
bool _closing{ false };
|
||||
|
||||
TerminalConnection::ITerminalConnection _connection{ nullptr };
|
||||
@@ -313,9 +320,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
uint64_t _owningHwnd{ 0 };
|
||||
|
||||
winrt::Windows::System::DispatcherQueue _dispatcher{ nullptr };
|
||||
std::shared_ptr<ThrottledFuncTrailing<>> _tsfTryRedrawCanvas;
|
||||
std::unique_ptr<til::throttled_func_trailing<>> _updatePatternLocations;
|
||||
std::shared_ptr<ThrottledFuncTrailing<Control::ScrollPositionChangedArgs>> _updateScrollBar;
|
||||
til::shared_mutex<SharedState> _shared;
|
||||
|
||||
til::point _contextMenuBufferPosition{ 0, 0 };
|
||||
|
||||
|
||||
@@ -149,26 +149,28 @@ namespace Microsoft.Terminal.Control
|
||||
Boolean ShouldShowSelectCommand();
|
||||
Boolean ShouldShowSelectOutput();
|
||||
|
||||
event FontSizeChangedEventArgs FontSizeChanged;
|
||||
|
||||
// These events are called from some background thread
|
||||
event Windows.Foundation.TypedEventHandler<Object, CopyToClipboardEventArgs> CopyToClipboard;
|
||||
event Windows.Foundation.TypedEventHandler<Object, TitleChangedEventArgs> TitleChanged;
|
||||
event Windows.Foundation.TypedEventHandler<Object, Object> WarningBell;
|
||||
event Windows.Foundation.TypedEventHandler<Object, Object> TabColorChanged;
|
||||
event Windows.Foundation.TypedEventHandler<Object, Object> BackgroundColorChanged;
|
||||
event Windows.Foundation.TypedEventHandler<Object, Object> TaskbarProgressChanged;
|
||||
event Windows.Foundation.TypedEventHandler<Object, Object> RendererEnteredErrorState;
|
||||
event Windows.Foundation.TypedEventHandler<Object, ShowWindowArgs> ShowWindowChanged;
|
||||
|
||||
// These events are always called from the UI thread (bugs aside)
|
||||
event FontSizeChangedEventArgs FontSizeChanged;
|
||||
event Windows.Foundation.TypedEventHandler<Object, ScrollPositionChangedArgs> ScrollPositionChanged;
|
||||
event Windows.Foundation.TypedEventHandler<Object, Object> CursorPositionChanged;
|
||||
event Windows.Foundation.TypedEventHandler<Object, Object> TaskbarProgressChanged;
|
||||
event Windows.Foundation.TypedEventHandler<Object, Object> ConnectionStateChanged;
|
||||
event Windows.Foundation.TypedEventHandler<Object, Object> HoveredHyperlinkChanged;
|
||||
event Windows.Foundation.TypedEventHandler<Object, Object> RendererEnteredErrorState;
|
||||
event Windows.Foundation.TypedEventHandler<Object, Object> SwapChainChanged;
|
||||
event Windows.Foundation.TypedEventHandler<Object, RendererWarningArgs> RendererWarning;
|
||||
event Windows.Foundation.TypedEventHandler<Object, NoticeEventArgs> RaiseNotice;
|
||||
event Windows.Foundation.TypedEventHandler<Object, TransparencyChangedEventArgs> TransparencyChanged;
|
||||
event Windows.Foundation.TypedEventHandler<Object, Object> ReceivedOutput;
|
||||
event Windows.Foundation.TypedEventHandler<Object, FoundResultsArgs> FoundMatch;
|
||||
event Windows.Foundation.TypedEventHandler<Object, ShowWindowArgs> ShowWindowChanged;
|
||||
event Windows.Foundation.TypedEventHandler<Object, UpdateSelectionMarkersEventArgs> UpdateSelectionMarkers;
|
||||
event Windows.Foundation.TypedEventHandler<Object, OpenHyperlinkEventArgs> OpenHyperlink;
|
||||
event Windows.Foundation.TypedEventHandler<Object, Object> CloseTerminalRequested;
|
||||
|
||||
@@ -997,7 +997,11 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
// _cursorTimer doesn't exist, and it would never turn on the
|
||||
// cursor. To mitigate, we'll initialize the cursor's 'on' state
|
||||
// with `_focused` here.
|
||||
_core.CursorOn(_focused);
|
||||
_core.CursorOn(_focused || DisplayCursorWhileBlurred);
|
||||
if (DisplayCursorWhileBlurred)
|
||||
{
|
||||
_cursorTimer->Start();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1873,7 +1877,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
TSFInputControl().NotifyFocusLeave();
|
||||
}
|
||||
|
||||
if (_cursorTimer)
|
||||
if (_cursorTimer && !DisplayCursorWhileBlurred)
|
||||
{
|
||||
_cursorTimer->Stop();
|
||||
_core.CursorOn(false);
|
||||
|
||||
@@ -182,6 +182,9 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
|
||||
WINRT_OBSERVABLE_PROPERTY(winrt::Windows::UI::Xaml::Media::Brush, BackgroundBrush, _PropertyChangedHandlers, nullptr);
|
||||
|
||||
public:
|
||||
til::property<bool> DisplayCursorWhileBlurred{ false };
|
||||
|
||||
private:
|
||||
friend struct TermControlT<TermControl>; // friend our parent so it can bind private event handlers
|
||||
|
||||
|
||||
@@ -111,6 +111,8 @@ namespace Microsoft.Terminal.Control
|
||||
// opacity set by the settings should call this instead.
|
||||
Double BackgroundOpacity { get; };
|
||||
|
||||
Boolean DisplayCursorWhileBlurred;
|
||||
|
||||
Windows.UI.Xaml.Media.Brush BackgroundBrush { get; };
|
||||
|
||||
void ColorSelection(SelectionColor fg, SelectionColor bg, Microsoft.Terminal.Core.MatchMode matchMode);
|
||||
|
||||
@@ -235,7 +235,7 @@ void Terminal::EraseScrollback()
|
||||
|
||||
bool Terminal::IsXtermBracketedPasteModeEnabled() const noexcept
|
||||
{
|
||||
return _bracketedPasteMode;
|
||||
return _systemMode.test(Mode::BracketedPaste);
|
||||
}
|
||||
|
||||
std::wstring_view Terminal::GetWorkingDirectory() noexcept
|
||||
|
||||
@@ -111,17 +111,14 @@ public:
|
||||
til::rect GetViewport() const noexcept override;
|
||||
void SetViewportPosition(const til::point position) noexcept override;
|
||||
void SetTextAttributes(const TextAttribute& attrs) noexcept override;
|
||||
void SetAutoWrapMode(const bool wrapAtEOL) noexcept override;
|
||||
bool GetAutoWrapMode() const noexcept override;
|
||||
void SetSystemMode(const Mode mode, const bool enabled) noexcept override;
|
||||
bool GetSystemMode(const Mode mode) const noexcept override;
|
||||
void WarningBell() override;
|
||||
bool GetLineFeedMode() const noexcept override;
|
||||
void SetWindowTitle(const std::wstring_view title) override;
|
||||
CursorType GetUserDefaultCursorStyle() const noexcept override;
|
||||
bool ResizeWindow(const til::CoordType width, const til::CoordType height) noexcept override;
|
||||
void SetConsoleOutputCP(const unsigned int codepage) noexcept override;
|
||||
unsigned int GetConsoleOutputCP() const noexcept override;
|
||||
void SetBracketedPasteMode(const bool enabled) noexcept override;
|
||||
bool GetBracketedPasteMode() const noexcept override;
|
||||
void CopyToClipboard(std::wstring_view content) override;
|
||||
void SetTaskbarProgress(const ::Microsoft::Console::VirtualTerminal::DispatchTypes::TaskbarState state, const size_t progress) override;
|
||||
void SetWorkingDirectory(std::wstring_view uri) override;
|
||||
@@ -320,10 +317,11 @@ private:
|
||||
|
||||
CursorType _defaultCursorShape = CursorType::Legacy;
|
||||
|
||||
til::enumset<Mode> _systemMode{ Mode::AutoWrap };
|
||||
|
||||
bool _snapOnInput = true;
|
||||
bool _altGrAliasing = true;
|
||||
bool _suppressApplicationTitle = false;
|
||||
bool _bracketedPasteMode = false;
|
||||
bool _trimBlockSelection = false;
|
||||
bool _autoMarkPrompts = false;
|
||||
|
||||
|
||||
@@ -61,15 +61,14 @@ void Terminal::SetTextAttributes(const TextAttribute& attrs) noexcept
|
||||
_activeBuffer().SetCurrentAttributes(attrs);
|
||||
}
|
||||
|
||||
void Terminal::SetAutoWrapMode(const bool /*wrapAtEOL*/) noexcept
|
||||
void Terminal::SetSystemMode(const Mode mode, const bool enabled) noexcept
|
||||
{
|
||||
// TODO: This will be needed to support DECAWM.
|
||||
_systemMode.set(mode, enabled);
|
||||
}
|
||||
|
||||
bool Terminal::GetAutoWrapMode() const noexcept
|
||||
bool Terminal::GetSystemMode(const Mode mode) const noexcept
|
||||
{
|
||||
// TODO: This will be needed to support DECAWM.
|
||||
return true;
|
||||
return _systemMode.test(mode);
|
||||
}
|
||||
|
||||
void Terminal::WarningBell()
|
||||
@@ -77,12 +76,6 @@ void Terminal::WarningBell()
|
||||
_pfnWarningBell();
|
||||
}
|
||||
|
||||
bool Terminal::GetLineFeedMode() const noexcept
|
||||
{
|
||||
// TODO: This will be needed to support LNM.
|
||||
return false;
|
||||
}
|
||||
|
||||
void Terminal::SetWindowTitle(const std::wstring_view title)
|
||||
{
|
||||
if (!_suppressApplicationTitle)
|
||||
@@ -114,16 +107,6 @@ unsigned int Terminal::GetConsoleOutputCP() const noexcept
|
||||
return CP_UTF8;
|
||||
}
|
||||
|
||||
void Terminal::SetBracketedPasteMode(const bool enabled) noexcept
|
||||
{
|
||||
_bracketedPasteMode = enabled;
|
||||
}
|
||||
|
||||
bool Terminal::GetBracketedPasteMode() const noexcept
|
||||
{
|
||||
return _bracketedPasteMode;
|
||||
}
|
||||
|
||||
void Terminal::CopyToClipboard(std::wstring_view content)
|
||||
{
|
||||
_pfnCopyToClipboard(content);
|
||||
|
||||
@@ -8,7 +8,17 @@
|
||||
using namespace ::winrt::Microsoft::Terminal::TerminalConnection;
|
||||
using namespace ::winrt::Windows::Foundation;
|
||||
|
||||
static constexpr std::wstring_view PreviewText{ L"Windows Terminal\r\nCopyright (c) Microsoft Corporation\r\n\nC:\\Windows\\Terminal> " };
|
||||
// clang-format off
|
||||
static constexpr std::wstring_view PreviewText{
|
||||
L"Windows Terminal\r\n"
|
||||
L"C:\\> \x1b[93m" L"git\x1b[m diff \x1b[90m-w\x1b[m\r\n"
|
||||
L"\x1b[1m" L"diff --git a/win b/win\x1b[m\r\n"
|
||||
L"\x1b[36m@@ -1 +1 @@\x1b[m\r\n"
|
||||
L"\x1b[31m- Windows Console\x1b[m\r\n"
|
||||
L"\x1b[32m+ Windows Terminal!\x1b[m\r\n"
|
||||
L"C:\\> \x1b[93mWrite-Host \x1b[36m\"\xd83c\xdf2f!\"\x1b[1D\x1b[m"
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
{
|
||||
@@ -16,8 +26,6 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
|
||||
void PreviewConnection::Start() noexcept
|
||||
{
|
||||
// First send a sequence to disable cursor blinking
|
||||
_TerminalOutputHandlers(L"\x1b[?12l");
|
||||
// Send the preview text
|
||||
_TerminalOutputHandlers(PreviewText);
|
||||
}
|
||||
|
||||
@@ -16,14 +16,9 @@ using namespace winrt::Windows::UI::Xaml::Navigation;
|
||||
|
||||
namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
{
|
||||
Profiles_Appearance::Profiles_Appearance() :
|
||||
_previewControl{ Control::TermControl(Model::TerminalSettings{}, nullptr, make<PreviewConnection>()) }
|
||||
Profiles_Appearance::Profiles_Appearance()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
_previewControl.IsEnabled(false);
|
||||
_previewControl.AllowFocusWhenDisabled(false);
|
||||
ControlPreview().Child(_previewControl);
|
||||
}
|
||||
|
||||
void Profiles_Appearance::OnNavigatedTo(const NavigationEventArgs& e)
|
||||
@@ -38,25 +33,23 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
ProfileViewModel::UpdateFontList();
|
||||
}
|
||||
|
||||
if (!_previewControl)
|
||||
{
|
||||
const auto settings = _Profile.TermSettings();
|
||||
_previewControl = Control::TermControl(settings, settings, make<PreviewConnection>());
|
||||
_previewControl.IsEnabled(false);
|
||||
_previewControl.AllowFocusWhenDisabled(false);
|
||||
_previewControl.DisplayCursorWhileBlurred(true);
|
||||
ControlPreview().Child(_previewControl);
|
||||
}
|
||||
|
||||
// Subscribe to some changes in the view model
|
||||
// These changes should force us to update our own set of "Current<Setting>" members,
|
||||
// and propagate those changes to the UI
|
||||
_ViewModelChangedRevoker = _Profile.PropertyChanged(winrt::auto_revoke, [=](auto&&, const PropertyChangedEventArgs& /*args*/) {
|
||||
_previewControl.UpdateControlSettings(_Profile.TermSettings(), _Profile.TermSettings());
|
||||
});
|
||||
|
||||
_ViewModelChangedRevoker = _Profile.PropertyChanged(winrt::auto_revoke, { this, &Profiles_Appearance::_onProfilePropertyChanged });
|
||||
// The Appearances object handles updating the values in the settings UI, but
|
||||
// we still need to listen to the changes here just to update the preview control
|
||||
_AppearanceViewModelChangedRevoker = _Profile.DefaultAppearance().PropertyChanged(winrt::auto_revoke, [=](auto&&, const PropertyChangedEventArgs& /*args*/) {
|
||||
_previewControl.UpdateControlSettings(_Profile.TermSettings(), _Profile.TermSettings());
|
||||
});
|
||||
|
||||
// There is a possibility that the control has not fully initialized yet,
|
||||
// so wait for it to initialize before updating the settings (so we know
|
||||
// that the renderer is set up)
|
||||
_previewControl.Initialized([&](auto&& /*s*/, auto&& /*e*/) {
|
||||
_previewControl.UpdateControlSettings(_Profile.TermSettings(), _Profile.TermSettings());
|
||||
});
|
||||
_AppearanceViewModelChangedRevoker = _Profile.DefaultAppearance().PropertyChanged(winrt::auto_revoke, { this, &Profiles_Appearance::_onProfilePropertyChanged });
|
||||
}
|
||||
|
||||
void Profiles_Appearance::OnNavigatedFrom(const NavigationEventArgs& /*e*/)
|
||||
@@ -74,4 +67,10 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
{
|
||||
_Profile.DeleteUnfocusedAppearance();
|
||||
}
|
||||
|
||||
void Profiles_Appearance::_onProfilePropertyChanged(const IInspectable&, const PropertyChangedEventArgs&) const
|
||||
{
|
||||
const auto settings = _Profile.TermSettings();
|
||||
_previewControl.UpdateControlSettings(settings, settings);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,7 +25,9 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
WINRT_PROPERTY(Editor::ProfileViewModel, Profile, nullptr);
|
||||
|
||||
private:
|
||||
Microsoft::Terminal::Control::TermControl _previewControl;
|
||||
void _onProfilePropertyChanged(const IInspectable&, const PropertyChangedEventArgs&) const;
|
||||
|
||||
Microsoft::Terminal::Control::TermControl _previewControl{ nullptr };
|
||||
Windows::UI::Xaml::Data::INotifyPropertyChanged::PropertyChanged_revoker _ViewModelChangedRevoker;
|
||||
Windows::UI::Xaml::Data::INotifyPropertyChanged::PropertyChanged_revoker _AppearanceViewModelChangedRevoker;
|
||||
Editor::IHostedInWindow _windowRoot;
|
||||
|
||||
@@ -47,8 +47,8 @@
|
||||
<!-- Control Preview -->
|
||||
<Border MaxWidth="{StaticResource StandardControlMaxWidth}">
|
||||
<Border x:Name="ControlPreview"
|
||||
Width="350"
|
||||
Height="160"
|
||||
Width="400"
|
||||
Height="180"
|
||||
Margin="0,0,0,12"
|
||||
HorizontalAlignment="Left"
|
||||
BorderBrush="{ThemeResource SystemControlForegroundBaseMediumLowBrush}"
|
||||
|
||||
@@ -140,7 +140,7 @@
|
||||
<comment>This is the header for a control that allows the user to set the currently selected color scheme as their default.</comment>
|
||||
</data>
|
||||
<data name="ColorScheme_SetAsDefault.HelpText" xml:space="preserve">
|
||||
<value>Apply this color scheme to all your profiles. This sets this color scheme in your 'Defaults' profile, it can still be overridden in each individual profile.</value>
|
||||
<value>Apply this color scheme to all your profiles, by default. Individual profiles can still select their own color schemes.</value>
|
||||
<comment>A description explaining how this control changes the user's default color scheme.</comment>
|
||||
</data>
|
||||
<data name="ColorScheme_Rename.Header" xml:space="preserve">
|
||||
@@ -232,8 +232,8 @@
|
||||
<comment>The header for a control allowing users to choose the app's language.</comment>
|
||||
</data>
|
||||
<data name="Globals_Language.HelpText" xml:space="preserve">
|
||||
<value>Sets an override for the app's preferred language.</value>
|
||||
<comment>A description explaining how this control changes the app's language.</comment>
|
||||
<value>Selects a display language for the application. This will override your default Windows interface language.</value>
|
||||
<comment>A description explaining how this control changes the app's language. {Locked="Windows"}</comment>
|
||||
</data>
|
||||
<data name="Globals_LanguageDefault" xml:space="preserve">
|
||||
<value>Use system default</value>
|
||||
@@ -252,7 +252,7 @@
|
||||
<comment>Header for a control to select position of newly created tabs.</comment>
|
||||
</data>
|
||||
<data name="Globals_NewTabPosition.HelpText" xml:space="preserve">
|
||||
<value>Specifies where new tabs appear in the tab row</value>
|
||||
<value>Specifies where new tabs appear in the tab row.</value>
|
||||
<comment>A description for what the "Position of newly created tabs" setting does.</comment>
|
||||
</data>
|
||||
<data name="Globals_NewTabPositionAfterLastTab.Content" xml:space="preserve">
|
||||
@@ -450,10 +450,6 @@
|
||||
<value>Use acrylic material in the tab row</value>
|
||||
<comment>Header for a control to toggle whether "acrylic material" is used. "Acrylic material" is a Microsoft-specific term: https://docs.microsoft.com/en-us/windows/apps/design/style/acrylic</comment>
|
||||
</data>
|
||||
<data name="Globals_AcrylicTabRow.HelpText" xml:space="preserve">
|
||||
<value>When checked, the tab row will have the acrylic material.</value>
|
||||
<comment>A description for the "Globals_AcrylicTabRow.Header" setting does. "Acrylic material" is a Microsoft-specific term: https://docs.microsoft.com/en-us/windows/apps/design/style/acrylic</comment>
|
||||
</data>
|
||||
<data name="Globals_ShowTitleInTitlebar.Header" xml:space="preserve">
|
||||
<value>Use active terminal title as application title</value>
|
||||
<comment>Header for a control to toggle whether the terminal's title is shown as the application title, or not.</comment>
|
||||
@@ -483,8 +479,8 @@
|
||||
<comment>Header for a control to toggle whether the app should launch when the user's machine starts up, or not.</comment>
|
||||
</data>
|
||||
<data name="Globals_StartOnUserLogin.HelpText" xml:space="preserve">
|
||||
<value>When enabled, this enables the launch of Terminal at machine startup.</value>
|
||||
<comment>A description for what the "start on user login" setting does. Presented near "Globals_StartOnUserLogin.Header".</comment>
|
||||
<value>Automatically launch Terminal when you log in to Windows.</value>
|
||||
<comment>A description for what the "start on user login" setting does. Presented near "Globals_StartOnUserLogin.Header". {Locked="Windows"}</comment>
|
||||
</data>
|
||||
<data name="Globals_CenterOnLaunchCentered" xml:space="preserve">
|
||||
<value>Centered</value>
|
||||
@@ -511,7 +507,7 @@
|
||||
<comment>Header for a control to choose how wide the tabs are.</comment>
|
||||
</data>
|
||||
<data name="Globals_TabWidthMode.HelpText" xml:space="preserve">
|
||||
<value>Compact will shrink unfocused tabs to the size of the icon.</value>
|
||||
<value>Compact will shrink inactive tabs to the size of the icon.</value>
|
||||
<comment>A description for what the "tab width mode" setting does. Presented near "Globals_TabWidthMode.Header". 'Compact' must match the value for <Globals_TabWidthModeCompact.Content>.</comment>
|
||||
</data>
|
||||
<data name="Globals_TabWidthModeCompact.Content" xml:space="preserve">
|
||||
@@ -527,13 +523,9 @@
|
||||
<comment>An option to choose from for the "tab width mode" setting. When selected, each tab adjusts its width to the content within the tab.</comment>
|
||||
</data>
|
||||
<data name="Globals_Theme.Header" xml:space="preserve">
|
||||
<value>Theme</value>
|
||||
<value>Application Theme</value>
|
||||
<comment>Header for a control to choose the theme colors used in the app.</comment>
|
||||
</data>
|
||||
<data name="Globals_Theme.HelpText" xml:space="preserve">
|
||||
<value>Sets the theme of the application.</value>
|
||||
<comment>A description for what the "theme" setting does. Presented near "Globals_Theme.Header".</comment>
|
||||
</data>
|
||||
<data name="Globals_ThemeDark.Content" xml:space="preserve">
|
||||
<value>Dark</value>
|
||||
<comment>An option to choose from for the "theme" setting. When selected, the app is in dark theme and darker colors are used throughout the app.</comment>
|
||||
@@ -563,8 +555,8 @@
|
||||
<comment>Header for a control to determine what delimiters to use to identify separate words during word selection.</comment>
|
||||
</data>
|
||||
<data name="Globals_WordDelimiters.HelpText" xml:space="preserve">
|
||||
<value>Symbols used to define boundaries between words.</value>
|
||||
<comment>A description for what the "word delimiters" setting does. Presented near "Globals_WordDelimiters.Header".</comment>
|
||||
<value>These symbols will be used when you double-click text in the terminal or activate "Mark mode".</value>
|
||||
<comment>A description for what the "word delimiters" setting does. Presented near "Globals_WordDelimiters.Header". "Mark" is used in the sense of "choosing something to interact with."</comment>
|
||||
</data>
|
||||
<data name="Nav_Appearance.Content" xml:space="preserve">
|
||||
<value>Appearance</value>
|
||||
@@ -619,8 +611,8 @@
|
||||
<comment>Header for a control to toggle whether the app treats ctrl+alt as the AltGr (also known as the Alt Graph) modifier key found on keyboards. {Locked="AltGr"}</comment>
|
||||
</data>
|
||||
<data name="Profile_AltGrAliasing.HelpText" xml:space="preserve">
|
||||
<value>By default Windows treats Ctrl+Alt as an alias for AltGr. When disabled, this behavior will be disabled.</value>
|
||||
<comment>A description for what the "AltGr aliasing" setting does. Presented near "Profile_AltGrAliasing.Header".</comment>
|
||||
<value>By default, Windows treats Ctrl+Alt as an alias for AltGr. This setting will override Windows' default behavior.</value>
|
||||
<comment>A description for what the "AltGr aliasing" setting does. Presented near "Profile_AltGrAliasing.Header". {Locked="Windows"}</comment>
|
||||
</data>
|
||||
<data name="Profile_AntialiasingMode.[using:Windows.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
|
||||
<value>Text antialiasing</value>
|
||||
@@ -786,10 +778,6 @@
|
||||
<value>Color scheme</value>
|
||||
<comment>Header for a control to select the scheme (or set) of colors used in the session. This is selected from a list of options managed by the user.</comment>
|
||||
</data>
|
||||
<data name="Profile_ColorScheme.HelpText" xml:space="preserve">
|
||||
<value>Name of the color scheme to use.</value>
|
||||
<comment>A description for what the "color scheme" setting does. Presented near "Profile_ColorScheme".</comment>
|
||||
</data>
|
||||
<data name="Profile_Commandline.[using:Windows.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
|
||||
<value>Command line</value>
|
||||
<comment>Name for a control to determine commandline executable (i.e. a .exe file) to run when a terminal session of this profile is launched.</comment>
|
||||
@@ -835,7 +823,7 @@
|
||||
<comment>An option to choose from for the "adjust indistinguishable colors" setting. When selected, we will adjust the text colors for visibility only when the colors are part of this profile's color scheme's color table.</comment>
|
||||
</data>
|
||||
<data name="Profile_AdjustIndistinguishableColorsAlways.Content" xml:space="preserve">
|
||||
<value>Always (More performance intensive)</value>
|
||||
<value>Always</value>
|
||||
<comment>An option to choose from for the "adjust indistinguishable colors" setting. When selected, we will adjust the text colors for visibility.</comment>
|
||||
</data>
|
||||
<data name="Profile_CursorShapeBar.Content" xml:space="preserve">
|
||||
@@ -870,10 +858,6 @@
|
||||
<value>Font face</value>
|
||||
<comment>Name for a control to select the font for text in the app.</comment>
|
||||
</data>
|
||||
<data name="Profile_FontFace.HelpText" xml:space="preserve">
|
||||
<value>Name of the font face used in the profile.</value>
|
||||
<comment>A description for what the "font face" setting does. Presented near "Profile_FontFace".</comment>
|
||||
</data>
|
||||
<data name="Profile_FontSize.Header" xml:space="preserve">
|
||||
<value>Font size</value>
|
||||
<comment>Header for a control to determine the size of the text in the app.</comment>
|
||||
@@ -895,7 +879,7 @@
|
||||
<comment>Header for a control that sets the text line height.</comment>
|
||||
</data>
|
||||
<data name="Profile_LineHeight.HelpText" xml:space="preserve">
|
||||
<value>Sets the height of each line in the terminal as a multiple of the font size. The default depends on your font and is usually around 1.2.</value>
|
||||
<value>Override the line height of the terminal. Measured as a multiple of the font size. The default value depends on your font and is usually around 1.2.</value>
|
||||
<comment>A description for what the "line height" setting does. Presented near "Profile_LineHeight".</comment>
|
||||
</data>
|
||||
<data name="Profile_LineHeightBox.PlaceholderText" xml:space="preserve">
|
||||
@@ -987,15 +971,15 @@
|
||||
<comment>Header for a control to toggle classic CRT display effects, which gives the terminal a retro look.</comment>
|
||||
</data>
|
||||
<data name="Profile_RetroTerminalEffect.HelpText" xml:space="preserve">
|
||||
<value>When enabled, enables retro terminal effects such as glowing text and scan lines.</value>
|
||||
<comment>A description for what the "retro terminal effects" setting does. Presented near "Profile_RetroTerminalEffect".</comment>
|
||||
<value>Show retro-style terminal effects such as glowing text and scan lines.</value>
|
||||
<comment>A description for what the "retro terminal effects" setting does. Presented near "Profile_RetroTerminalEffect". "Retro" is a common English prefix that suggests a nostalgic, dated appearance.</comment>
|
||||
</data>
|
||||
<data name="Profile_AdjustIndistinguishableColors.Header" xml:space="preserve">
|
||||
<value>Automatically adjust lightness of indistinguishable text</value>
|
||||
<comment>Header for a control to toggle if we should adjust the foreground color's lightness to make it more visible when necessary, based on the background color.</comment>
|
||||
</data>
|
||||
<data name="Profile_AdjustIndistinguishableColors.HelpText" xml:space="preserve">
|
||||
<value>When enabled, enables automatic adjustment of indistinguishable colors, which will, only when necessary, adjust the foreground color's lightness to make it more visible (based on the background color).</value>
|
||||
<value>Automatically brightens or darkens text to make it more visible. Even when enabled, this adjustment will only occur when a combination of foreground and background colors would result in poor contrast.</value>
|
||||
<comment>A description for what the "adjust indistinguishable colors" setting does. Presented near "Profile_AdjustIndistinguishableColors".</comment>
|
||||
</data>
|
||||
<data name="Profile_ScrollbarVisibility.[using:Windows.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
|
||||
@@ -1055,8 +1039,8 @@
|
||||
<comment>Header for a control to toggle changes in the app title.</comment>
|
||||
</data>
|
||||
<data name="Profile_SuppressApplicationTitle.HelpText" xml:space="preserve">
|
||||
<value>Use the tab title to override the default title of the tab and suppress any title change messages from the application.</value>
|
||||
<comment>A description for what the "suppress application title" setting does. Presented near "Profile_SuppressApplicationTitle".</comment>
|
||||
<value>Ignore application requests to change the title (OSC 2).</value>
|
||||
<comment>A description for what the "suppress application title" setting does. Presented near "Profile_SuppressApplicationTitle". "OSC 2" is a technical term that is understood in the industry. {Locked="OSC 2"}</comment>
|
||||
</data>
|
||||
<data name="Profile_TabTitle.[using:Windows.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
|
||||
<value>Tab title</value>
|
||||
@@ -1095,7 +1079,7 @@
|
||||
<comment>A supplementary setting to the "background image" setting. When enabled, the OS desktop wallpaper is used as the background image. Presented near "Profile_BackgroundImage".</comment>
|
||||
</data>
|
||||
<data name="Profile_UseDesktopImage.[using:Windows.UI.Xaml.Controls]ToolTipService.ToolTip" xml:space="preserve">
|
||||
<value>When enabled, use the desktop wallpaper image as the background image for the terminal.</value>
|
||||
<value>Use the desktop wallpaper image as the background image for the terminal.</value>
|
||||
<comment>A description for what the supplementary "use desktop image" setting does. Presented near "Profile_UseDesktopImage".</comment>
|
||||
</data>
|
||||
<data name="Settings_ResetSettingsButton.Content" xml:space="preserve">
|
||||
@@ -1370,7 +1354,7 @@
|
||||
<comment>Header for a control to choose how the tab switcher operates.</comment>
|
||||
</data>
|
||||
<data name="Globals_TabSwitcherMode.HelpText" xml:space="preserve">
|
||||
<value>Defines the terminal behavior when switching tabs with the keyboard.</value>
|
||||
<value>Selects which interface will be used when you switch tabs using the keyboard.</value>
|
||||
<comment>A description for what the "tab switcher mode" setting does. Presented near "Globals_TabSwitcherMode.Header".</comment>
|
||||
</data>
|
||||
<data name="Globals_TabSwitcherModeMru.Content" xml:space="preserve">
|
||||
@@ -1386,13 +1370,9 @@
|
||||
<comment>An option to choose from for the "tab switcher mode" setting. The tab switcher overlay is hidden and does not appear in a separate window.</comment>
|
||||
</data>
|
||||
<data name="Globals_CopyFormat.Header" xml:space="preserve">
|
||||
<value>Text format when copying</value>
|
||||
<value>Text formats to copy to the clipboard</value>
|
||||
<comment>Header for a control to select the format of copied text.</comment>
|
||||
</data>
|
||||
<data name="Globals_CopyFormat.HelpText" xml:space="preserve">
|
||||
<value>Defines the type of formatting in which selected text is copied to your clipboard.</value>
|
||||
<comment>A description for what the "copy formatting" setting does. Presented near "Globals_CopyFormat.Header".</comment>
|
||||
</data>
|
||||
<data name="Globals_CopyFormatNone.Content" xml:space="preserve">
|
||||
<value>Plain text only</value>
|
||||
<comment>An option to choose from for the "copy formatting" setting. Store only plain text data.</comment>
|
||||
@@ -1571,10 +1551,10 @@
|
||||
</data>
|
||||
<data name="Globals_AutoHideWindow.Header" xml:space="preserve">
|
||||
<value>Automatically hide window</value>
|
||||
<comment>Header for a control to toggle the "Automatically hide window" setting. If enabled, the window will be hidden as soon as it loses focus.</comment>
|
||||
<comment>Header for a control to toggle the "Automatically hide window" setting. If enabled, the terminal will be hidden as soon as you switch to another window.</comment>
|
||||
</data>
|
||||
<data name="Globals_AutoHideWindow.HelpText" xml:space="preserve">
|
||||
<value>If enabled, the window will be hidden as soon as it loses focus.</value>
|
||||
<value>If enabled, the terminal will be hidden as soon as you switch to another window.</value>
|
||||
<comment>A description for what the "Automatically hide window" setting does.</comment>
|
||||
</data>
|
||||
<data name="ColorScheme_DeleteDisclaimerInBox" xml:space="preserve">
|
||||
|
||||
@@ -157,7 +157,7 @@ static void _accumulateTraditionalLayoutPowerShellInstancesInDirectory(std::wstr
|
||||
const auto executable = versionedPath / PWSH_EXE;
|
||||
if (std::filesystem::exists(executable))
|
||||
{
|
||||
const auto preview = versionedPath.filename().wstring().find(L"-preview") != std::wstring::npos;
|
||||
const auto preview = versionedPath.filename().native().find(L"-preview") != std::wstring::npos;
|
||||
const auto previewFlag = preview ? PowerShellFlags::Preview : PowerShellFlags::None;
|
||||
out.emplace_back(PowerShellInstance{ std::stoi(versionedPath.filename()),
|
||||
PowerShellFlags::Traditional | flags | previewFlag,
|
||||
|
||||
@@ -44,11 +44,12 @@ std::wstring VsDevCmdGenerator::GetProfileCommandLine(const VsSetupConfiguration
|
||||
commandLine.append(GetDevCmdScriptPath(instance));
|
||||
// The "-startdir" parameter will prevent "vsdevcmd" from automatically
|
||||
// setting the shell path so the path in the profile will be used instead.
|
||||
commandLine.append(LR"(" -startdir=none)");
|
||||
#if defined(_M_ARM64)
|
||||
commandLine.append(LR"(" -arch=arm64 -host_arch=x64)");
|
||||
commandLine.append(LR"(" -startdir=none -arch=arm64 -host_arch=x64)");
|
||||
#elif defined(_M_AMD64)
|
||||
commandLine.append(LR"(" -arch=x64 -host_arch=x64)");
|
||||
commandLine.append(LR"(" -startdir=none -arch=x64 -host_arch=x64)");
|
||||
#else
|
||||
commandLine.append(LR"(" -startdir=none)");
|
||||
#endif
|
||||
return commandLine;
|
||||
}
|
||||
|
||||
@@ -46,6 +46,7 @@ Licensed under the MIT license.
|
||||
|
||||
// Manually include til after we include Windows.Foundation to give it winrt superpowers
|
||||
#include "til.h"
|
||||
#include <til/mutex.h>
|
||||
#include <til/winrt.h>
|
||||
|
||||
#include "ThrottledFunc.h"
|
||||
|
||||
@@ -65,20 +65,6 @@ AppHost::AppHost(const winrt::TerminalApp::AppLogic& logic,
|
||||
std::placeholders::_2);
|
||||
_window->SetCreateCallback(pfn);
|
||||
|
||||
// Event handlers:
|
||||
// MAKE SURE THEY ARE ALL:
|
||||
// * winrt::auto_revoke
|
||||
// * revoked manually in the dtor before the window is nulled out.
|
||||
//
|
||||
// If you don't, then it's possible for them to get triggered as the app is
|
||||
// tearing down, after we've nulled out the window, during the dtor. That
|
||||
// can cause unexpected AV's everywhere.
|
||||
//
|
||||
// _window callbacks don't need to be treated this way, because:
|
||||
// * IslandWindow isn't a WinRT type (so it doesn't have neat revokers like this)
|
||||
// * This particular bug scenario applies when we've already freed the window.
|
||||
//
|
||||
// (Most of these events are actually set up in AppHost::Initialize)
|
||||
_window->MouseScrolled({ this, &AppHost::_WindowMouseWheeled });
|
||||
_window->WindowActivated({ this, &AppHost::_WindowActivated });
|
||||
_window->WindowMoved({ this, &AppHost::_WindowMoved });
|
||||
@@ -91,21 +77,6 @@ AppHost::AppHost(const winrt::TerminalApp::AppLogic& logic,
|
||||
_window->MakeWindow();
|
||||
}
|
||||
|
||||
AppHost::~AppHost()
|
||||
{
|
||||
// destruction order is important for proper teardown here
|
||||
|
||||
// revoke ALL our event handlers. There's a large class of bugs where we
|
||||
// might get a callback to one of these when we call app.Close() below. Make
|
||||
// sure to revoke these first, so we won't get any more callbacks, then null
|
||||
// out the window, then close the app.
|
||||
_revokers = {};
|
||||
|
||||
_showHideWindowThrottler.reset();
|
||||
|
||||
_window = nullptr;
|
||||
}
|
||||
|
||||
bool AppHost::OnDirectKeyEvent(const uint32_t vkey, const uint8_t scanCode, const bool down)
|
||||
{
|
||||
if (_windowLogic)
|
||||
@@ -177,7 +148,7 @@ void AppHost::_HandleCommandlineArgs(const Remoting::WindowRequestedArgs& window
|
||||
}
|
||||
else if (args)
|
||||
{
|
||||
const auto result = _windowLogic.SetStartupCommandline(args.Commandline());
|
||||
const auto result = _windowLogic.SetStartupCommandline(args.Commandline(), args.CurrentDirectory());
|
||||
const auto message = _windowLogic.ParseCommandlineMessage();
|
||||
if (!message.empty())
|
||||
{
|
||||
@@ -443,6 +414,15 @@ void AppHost::Initialize()
|
||||
_window->OnAppInitialized();
|
||||
}
|
||||
|
||||
void AppHost::Close()
|
||||
{
|
||||
// After calling _window->Close() we should avoid creating more WinUI related actions.
|
||||
// I suspect WinUI wouldn't like that very much. As such unregister all event handlers first.
|
||||
_revokers = {};
|
||||
_showHideWindowThrottler.reset();
|
||||
_window->Close();
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Called every time when the active tab's title changes. We'll also fire off
|
||||
// a window message so we can update the window's title on the main thread,
|
||||
@@ -498,7 +478,7 @@ void AppHost::LastTabClosed(const winrt::Windows::Foundation::IInspectable& /*se
|
||||
// event handler finishes.
|
||||
_windowManager.SignalClose(_peasant);
|
||||
|
||||
_window->Close();
|
||||
PostQuitMessage(0);
|
||||
}
|
||||
|
||||
LaunchPosition AppHost::_GetWindowLaunchPosition()
|
||||
@@ -1110,10 +1090,7 @@ void AppHost::_ShowWindowChanged(const winrt::Windows::Foundation::IInspectable&
|
||||
// should prevent scenarios where the Terminal window state and PTY window
|
||||
// state get de-sync'd, and cause the window to minimize/restore constantly
|
||||
// in a loop.
|
||||
if (_showHideWindowThrottler)
|
||||
{
|
||||
_showHideWindowThrottler->Run(args.ShowOrHide());
|
||||
}
|
||||
_showHideWindowThrottler->Run(args.ShowOrHide());
|
||||
}
|
||||
|
||||
void AppHost::_SummonWindowRequested(const winrt::Windows::Foundation::IInspectable& sender,
|
||||
|
||||
@@ -13,11 +13,11 @@ public:
|
||||
winrt::Microsoft::Terminal::Remoting::WindowRequestedArgs args,
|
||||
const winrt::Microsoft::Terminal::Remoting::WindowManager& manager,
|
||||
const winrt::Microsoft::Terminal::Remoting::Peasant& peasant) noexcept;
|
||||
~AppHost();
|
||||
|
||||
void AppTitleChanged(const winrt::Windows::Foundation::IInspectable& sender, winrt::hstring newTitle);
|
||||
void LastTabClosed(const winrt::Windows::Foundation::IInspectable& sender, const winrt::TerminalApp::LastTabClosedEventArgs& args);
|
||||
void Initialize();
|
||||
void Close();
|
||||
bool OnDirectKeyEvent(const uint32_t vkey, const uint8_t scanCode, const bool down);
|
||||
void SetTaskbarProgress(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Windows::Foundation::IInspectable& args);
|
||||
|
||||
|
||||
@@ -37,7 +37,66 @@ IslandWindow::IslandWindow() noexcept :
|
||||
|
||||
IslandWindow::~IslandWindow()
|
||||
{
|
||||
_source.Close();
|
||||
Close();
|
||||
}
|
||||
|
||||
void IslandWindow::Close()
|
||||
{
|
||||
static const bool isWindows11 = []() {
|
||||
OSVERSIONINFOEXW osver{};
|
||||
osver.dwOSVersionInfoSize = sizeof(osver);
|
||||
osver.dwBuildNumber = 22000;
|
||||
|
||||
DWORDLONG dwlConditionMask = 0;
|
||||
VER_SET_CONDITION(dwlConditionMask, VER_BUILDNUMBER, VER_GREATER_EQUAL);
|
||||
|
||||
if (VerifyVersionInfoW(&osver, VER_BUILDNUMBER, dwlConditionMask) != FALSE)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}();
|
||||
|
||||
if (!isWindows11)
|
||||
{
|
||||
// BODGY
|
||||
// ____ ____ _____ _______ __
|
||||
// | _ \ / __ \| __ \ / ____\ \ / /
|
||||
// | |_) | | | | | | | | __ \ \_/ /
|
||||
// | _ <| | | | | | | | |_ | \ /
|
||||
// | |_) | |__| | |__| | |__| | | |
|
||||
// |____/ \____/|_____/ \_____| |_|
|
||||
//
|
||||
// There's a bug in Windows 10 where closing a DesktopWindowXamlSource
|
||||
// on any thread will free an internal static resource that's used by
|
||||
// XAML for the entire process. This would result in closing window
|
||||
// essentially causing the entire app to crash.
|
||||
//
|
||||
// To avoid this, leak the XAML island. We only need to leak this on
|
||||
// Windows 10, since the bug is fixed in Windows 11.
|
||||
//
|
||||
// See GH #15384, MSFT:32109540
|
||||
auto a{ _source };
|
||||
winrt::detach_abi(_source);
|
||||
|
||||
// </BODGY>
|
||||
}
|
||||
|
||||
// GH#15454: Unset the user data for the window. This will prevent future
|
||||
// callbacks that come onto our window message loop from being sent to the
|
||||
// IslandWindow (or other derived class's) implementation.
|
||||
//
|
||||
// Specifically, this prevents a pending coroutine from being able to call
|
||||
// something like ShowWindow, and have that come back on the IslandWindow
|
||||
// message loop, where it'll end up asking XAML something that XAML is no
|
||||
// longer able to answer.
|
||||
SetWindowLongPtr(_window.get(), GWLP_USERDATA, 0);
|
||||
|
||||
if (_source)
|
||||
{
|
||||
_source.Close();
|
||||
_source = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
HWND IslandWindow::GetInteropHandle() const
|
||||
@@ -89,17 +148,6 @@ void IslandWindow::MakeWindow() noexcept
|
||||
WINRT_ASSERT(_window);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Called when no tab is remaining to close the window.
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void IslandWindow::Close()
|
||||
{
|
||||
PostQuitMessage(0);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Set a callback to be called when we process a WM_CREATE message. This gives
|
||||
// the AppHost a chance to resize the window to the proper size.
|
||||
|
||||
@@ -20,7 +20,7 @@ public:
|
||||
virtual ~IslandWindow() override;
|
||||
|
||||
virtual void MakeWindow() noexcept;
|
||||
void Close();
|
||||
virtual void Close();
|
||||
virtual void OnSize(const UINT width, const UINT height);
|
||||
HWND GetInteropHandle() const;
|
||||
|
||||
|
||||
@@ -26,7 +26,13 @@ NonClientIslandWindow::NonClientIslandWindow(const ElementTheme& requestedTheme)
|
||||
{
|
||||
}
|
||||
|
||||
NonClientIslandWindow::~NonClientIslandWindow() = default;
|
||||
void NonClientIslandWindow::Close()
|
||||
{
|
||||
// Avoid further callbacks into XAML/WinUI-land after we've Close()d the DesktopWindowXamlSource
|
||||
// inside `IslandWindow::Close()`. XAML thanks us for doing that by not crashing. Thank you XAML.
|
||||
SetWindowLongPtr(_dragBarWindow.get(), GWLP_USERDATA, 0);
|
||||
IslandWindow::Close();
|
||||
}
|
||||
|
||||
static constexpr const wchar_t* dragBarClassName{ L"DRAG_BAR_WINDOW_CLASS" };
|
||||
|
||||
|
||||
@@ -30,8 +30,8 @@ public:
|
||||
static constexpr const int topBorderVisibleHeight = 1;
|
||||
|
||||
NonClientIslandWindow(const winrt::Windows::UI::Xaml::ElementTheme& requestedTheme) noexcept;
|
||||
virtual ~NonClientIslandWindow() override;
|
||||
|
||||
virtual void Close() override;
|
||||
void MakeWindow() noexcept override;
|
||||
virtual void OnSize(const UINT width, const UINT height) override;
|
||||
|
||||
|
||||
@@ -37,6 +37,20 @@ WindowEmperor::WindowEmperor() noexcept :
|
||||
});
|
||||
|
||||
_dispatcher = winrt::Windows::System::DispatcherQueue::GetForCurrentThread();
|
||||
|
||||
// BODGY
|
||||
//
|
||||
// There's a mysterious crash in XAML on Windows 10 if you just let the App
|
||||
// get dtor'd. By all accounts, it doesn't make sense. To mitigate this, we
|
||||
// need to intentionally leak a reference to our App. Crazily, if you just
|
||||
// let the app get cleaned up with the rest of the process when the process
|
||||
// exits, then it doesn't crash. But if you let it get explicitly dtor'd, it
|
||||
// absolutely will crash on exit.
|
||||
//
|
||||
// GH#15410 has more details.
|
||||
|
||||
auto a{ _app };
|
||||
::winrt::detach_abi(a);
|
||||
}
|
||||
|
||||
WindowEmperor::~WindowEmperor()
|
||||
@@ -71,7 +85,17 @@ bool WindowEmperor::HandleCommandlineArgs()
|
||||
{
|
||||
std::vector<winrt::hstring> args;
|
||||
_buildArgsFromCommandline(args);
|
||||
auto cwd{ wil::GetCurrentDirectoryW<std::wstring>() };
|
||||
const auto cwd{ wil::GetCurrentDirectoryW<std::wstring>() };
|
||||
|
||||
{
|
||||
// ALWAYS change the _real_ CWD of the Terminal to system32, so that we
|
||||
// don't lock the directory we were spawned in.
|
||||
std::wstring system32{};
|
||||
if (SUCCEEDED_LOG(wil::GetSystemDirectoryW<std::wstring>(system32)))
|
||||
{
|
||||
LOG_IF_WIN32_BOOL_FALSE(SetCurrentDirectoryW(system32.c_str()));
|
||||
}
|
||||
}
|
||||
|
||||
// Get the requested initial state of the window from our startup info. For
|
||||
// something like `start /min`, this will set the wShowWindow member to
|
||||
@@ -99,7 +123,7 @@ bool WindowEmperor::HandleCommandlineArgs()
|
||||
if (!res.Message.empty())
|
||||
{
|
||||
AppHost::s_DisplayMessageBox(res);
|
||||
ExitThread(res.ExitCode);
|
||||
std::quick_exit(res.ExitCode);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -140,10 +164,16 @@ void WindowEmperor::_createNewWindowThread(const Remoting::WindowRequestedArgs&
|
||||
std::thread t([weakThis, window]() {
|
||||
try
|
||||
{
|
||||
auto cleanup = wil::scope_exit([&]() {
|
||||
const auto decrementWindowCount = wil::scope_exit([&]() {
|
||||
if (auto self{ weakThis.lock() })
|
||||
{
|
||||
self->_windowExitedHandler(window->Peasant().GetID());
|
||||
self->_decrementWindowCount();
|
||||
}
|
||||
});
|
||||
auto removeWindow = wil::scope_exit([&]() {
|
||||
if (auto self{ weakThis.lock() })
|
||||
{
|
||||
self->_removeWindow(window->PeasantID());
|
||||
}
|
||||
});
|
||||
|
||||
@@ -160,7 +190,7 @@ void WindowEmperor::_createNewWindowThread(const Remoting::WindowRequestedArgs&
|
||||
// remove the window from our list of windows, before we release the
|
||||
// AppHost (and subsequently, the host's Logic() member that we use
|
||||
// elsewhere).
|
||||
cleanup.reset();
|
||||
removeWindow.reset();
|
||||
|
||||
// Now that we no longer care about this thread's window, let it
|
||||
// release it's app host and flush the rest of the XAML queue.
|
||||
@@ -204,17 +234,20 @@ void WindowEmperor::_windowStartedHandlerPostXAML(const std::shared_ptr<WindowTh
|
||||
lockedWindows->push_back(sender);
|
||||
}
|
||||
}
|
||||
void WindowEmperor::_windowExitedHandler(uint64_t senderID)
|
||||
|
||||
void WindowEmperor::_removeWindow(uint64_t senderID)
|
||||
{
|
||||
auto lockedWindows{ _windows.lock() };
|
||||
|
||||
// find the window in _windows who's peasant's Id matches the peasant's Id
|
||||
// and remove it
|
||||
std::erase_if(*lockedWindows,
|
||||
[&](const auto& w) {
|
||||
return w->Peasant().GetID() == senderID;
|
||||
});
|
||||
std::erase_if(*lockedWindows, [&](const auto& w) {
|
||||
return w->PeasantID() == senderID;
|
||||
});
|
||||
}
|
||||
|
||||
void WindowEmperor::_decrementWindowCount()
|
||||
{
|
||||
// When we run out of windows, exit our process if and only if:
|
||||
// * We're not allowed to run headless OR
|
||||
// * we've explicitly been told to "quit", which should fully exit the Terminal.
|
||||
@@ -226,6 +259,7 @@ void WindowEmperor::_windowExitedHandler(uint64_t senderID)
|
||||
_close();
|
||||
}
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Set up all sorts of handlers now that we've determined that we're a process
|
||||
// that will end up hosting the windows. These include:
|
||||
@@ -276,24 +310,6 @@ void WindowEmperor::_becomeMonarch()
|
||||
// We want at least some delay to prevent the first save from overwriting
|
||||
_getWindowLayoutThrottler.emplace(std::move(std::chrono::seconds(10)), std::move([this]() { _saveWindowLayoutsRepeat(); }));
|
||||
_getWindowLayoutThrottler.value()();
|
||||
|
||||
// BODGY
|
||||
//
|
||||
// We've got a weird crash that happens terribly inconsistently, but pretty
|
||||
// readily on migrie's laptop, only in Debug mode. Apparently, there's some
|
||||
// weird ref-counting magic that goes on during teardown, and our
|
||||
// Application doesn't get closed quite right, which can cause us to crash
|
||||
// into the debugger. This of course, only happens on exit, and happens
|
||||
// somewhere in the XamlHost.dll code.
|
||||
//
|
||||
// Crazily, if we _manually leak the Application_ here, then the crash
|
||||
// doesn't happen. This doesn't matter, because we really want the
|
||||
// Application to live for _the entire lifetime of the process_, so the only
|
||||
// time when this object would actually need to get cleaned up is _during
|
||||
// exit_. So we can safely leak this Application object, and have it just
|
||||
// get cleaned up normally when our process exits.
|
||||
auto a{ _app };
|
||||
::winrt::detach_abi(a);
|
||||
}
|
||||
|
||||
// sender and args are always nullptr
|
||||
|
||||
@@ -55,7 +55,8 @@ private:
|
||||
bool _quitting{ false };
|
||||
|
||||
void _windowStartedHandlerPostXAML(const std::shared_ptr<WindowThread>& sender);
|
||||
void _windowExitedHandler(uint64_t senderID);
|
||||
void _removeWindow(uint64_t senderID);
|
||||
void _decrementWindowCount();
|
||||
|
||||
void _becomeMonarch();
|
||||
void _numberOfWindowsChanged(const winrt::Windows::Foundation::IInspectable&, const winrt::Windows::Foundation::IInspectable&);
|
||||
|
||||
@@ -42,7 +42,8 @@ int WindowThread::RunMessagePump()
|
||||
|
||||
void WindowThread::RundownForExit()
|
||||
{
|
||||
_host = nullptr;
|
||||
_host->Close();
|
||||
|
||||
// !! LOAD BEARING !!
|
||||
//
|
||||
// Make sure to finish pumping all the messages for our thread here. We
|
||||
@@ -128,7 +129,8 @@ int WindowThread::_messagePump()
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
winrt::Microsoft::Terminal::Remoting::Peasant WindowThread::Peasant()
|
||||
|
||||
uint64_t WindowThread::PeasantID()
|
||||
{
|
||||
return _peasant;
|
||||
return _peasant.GetID();
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ public:
|
||||
int RunMessagePump();
|
||||
void RundownForExit();
|
||||
|
||||
winrt::Microsoft::Terminal::Remoting::Peasant Peasant();
|
||||
uint64_t PeasantID();
|
||||
|
||||
WINRT_CALLBACK(UpdateSettingsRequested, winrt::delegate<void()>);
|
||||
|
||||
|
||||
@@ -114,40 +114,49 @@ void ConhostInternalGetSet::SetTextAttributes(const TextAttribute& attrs)
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Sets the ENABLE_WRAP_AT_EOL_OUTPUT mode. This controls whether the cursor moves
|
||||
// to the beginning of the next row when it reaches the end of the current row.
|
||||
// - Sets the state of one of the system modes.
|
||||
// Arguments:
|
||||
// - wrapAtEOL - set to true to wrap, false to overwrite the last character.
|
||||
// - mode - The mode being updated.
|
||||
// - enabled - True to enable the mode, false to disable it.
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void ConhostInternalGetSet::SetAutoWrapMode(const bool wrapAtEOL)
|
||||
void ConhostInternalGetSet::SetSystemMode(const Mode mode, const bool enabled)
|
||||
{
|
||||
auto& outputMode = _io.GetActiveOutputBuffer().OutputMode;
|
||||
WI_UpdateFlag(outputMode, ENABLE_WRAP_AT_EOL_OUTPUT, wrapAtEOL);
|
||||
switch (mode)
|
||||
{
|
||||
case Mode::AutoWrap:
|
||||
WI_UpdateFlag(_io.GetActiveOutputBuffer().OutputMode, ENABLE_WRAP_AT_EOL_OUTPUT, enabled);
|
||||
break;
|
||||
case Mode::LineFeed:
|
||||
WI_UpdateFlag(_io.GetActiveOutputBuffer().OutputMode, DISABLE_NEWLINE_AUTO_RETURN, !enabled);
|
||||
break;
|
||||
case Mode::BracketedPaste:
|
||||
ServiceLocator::LocateGlobals().getConsoleInformation().SetBracketedPasteMode(enabled);
|
||||
break;
|
||||
default:
|
||||
THROW_HR(E_INVALIDARG);
|
||||
}
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Retrieves the current state of ENABLE_WRAP_AT_EOL_OUTPUT mode.
|
||||
// - Retrieves the current state of one of the system modes.
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// - mode - The mode being queried.
|
||||
// Return Value:
|
||||
// - true if the mode is enabled. false otherwise.
|
||||
bool ConhostInternalGetSet::GetAutoWrapMode() const
|
||||
bool ConhostInternalGetSet::GetSystemMode(const Mode mode) const
|
||||
{
|
||||
const auto outputMode = _io.GetActiveOutputBuffer().OutputMode;
|
||||
return WI_IsFlagSet(outputMode, ENABLE_WRAP_AT_EOL_OUTPUT);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Retrieves the current Line Feed/New Line (LNM) mode.
|
||||
// Arguments:
|
||||
// - None
|
||||
// Return Value:
|
||||
// - true if a line feed also produces a carriage return. false otherwise.
|
||||
bool ConhostInternalGetSet::GetLineFeedMode() const
|
||||
{
|
||||
auto& screenInfo = _io.GetActiveOutputBuffer();
|
||||
return WI_IsFlagClear(screenInfo.OutputMode, DISABLE_NEWLINE_AUTO_RETURN);
|
||||
switch (mode)
|
||||
{
|
||||
case Mode::AutoWrap:
|
||||
return WI_IsFlagSet(_io.GetActiveOutputBuffer().OutputMode, ENABLE_WRAP_AT_EOL_OUTPUT);
|
||||
case Mode::LineFeed:
|
||||
return WI_IsFlagClear(_io.GetActiveOutputBuffer().OutputMode, DISABLE_NEWLINE_AUTO_RETURN);
|
||||
case Mode::BracketedPaste:
|
||||
return ServiceLocator::LocateGlobals().getConsoleInformation().GetBracketedPasteMode();
|
||||
default:
|
||||
THROW_HR(E_INVALIDARG);
|
||||
}
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
@@ -245,29 +254,6 @@ unsigned int ConhostInternalGetSet::GetConsoleOutputCP() const
|
||||
return ServiceLocator::LocateGlobals().getConsoleInformation().OutputCP;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Sets the XTerm bracketed paste mode. This controls whether pasted content is
|
||||
// bracketed with control sequences to differentiate it from typed text.
|
||||
// Arguments:
|
||||
// - enable - set to true to enable bracketing, false to disable.
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void ConhostInternalGetSet::SetBracketedPasteMode(const bool enabled)
|
||||
{
|
||||
ServiceLocator::LocateGlobals().getConsoleInformation().SetBracketedPasteMode(enabled);
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Gets the current state of XTerm bracketed paste mode.
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - true if the mode is enabled, false if not.
|
||||
bool ConhostInternalGetSet::GetBracketedPasteMode() const
|
||||
{
|
||||
return ServiceLocator::LocateGlobals().getConsoleInformation().GetBracketedPasteMode();
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Copies the given content to the clipboard.
|
||||
// Arguments:
|
||||
|
||||
@@ -38,13 +38,11 @@ public:
|
||||
|
||||
void SetTextAttributes(const TextAttribute& attrs) override;
|
||||
|
||||
void SetAutoWrapMode(const bool wrapAtEOL) override;
|
||||
bool GetAutoWrapMode() const override;
|
||||
void SetSystemMode(const Mode mode, const bool enabled) override;
|
||||
bool GetSystemMode(const Mode mode) const override;
|
||||
|
||||
void WarningBell() override;
|
||||
|
||||
bool GetLineFeedMode() const override;
|
||||
|
||||
void SetWindowTitle(const std::wstring_view title) override;
|
||||
|
||||
void UseAlternateScreenBuffer() override;
|
||||
@@ -60,8 +58,6 @@ public:
|
||||
void SetConsoleOutputCP(const unsigned int codepage) override;
|
||||
unsigned int GetConsoleOutputCP() const override;
|
||||
|
||||
void SetBracketedPasteMode(const bool enabled) override;
|
||||
bool GetBracketedPasteMode() const override;
|
||||
void CopyToClipboard(const std::wstring_view content) override;
|
||||
void SetTaskbarProgress(const ::Microsoft::Console::VirtualTerminal::DispatchTypes::TaskbarState state, const size_t progress) override;
|
||||
void SetWorkingDirectory(const std::wstring_view uri) override;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -313,6 +313,15 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
|
||||
|
||||
if (valueNameSize)
|
||||
{
|
||||
const bool isPathVar = is_path_var(valueName);
|
||||
|
||||
// On some systems we've seen path variables that are REG_SZ instead
|
||||
// of REG_EXPAND_SZ. We should always treat them as REG_EXPAND_SZ.
|
||||
if (isPathVar && type == REG_SZ)
|
||||
{
|
||||
type = REG_EXPAND_SZ;
|
||||
}
|
||||
|
||||
std::wstring data;
|
||||
if (pass == 0 && (type == REG_SZ) && valueDataSize >= sizeof(wchar_t))
|
||||
{
|
||||
@@ -335,7 +344,7 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
|
||||
|
||||
if (!data.empty())
|
||||
{
|
||||
if (is_path_var(valueName))
|
||||
if (isPathVar)
|
||||
{
|
||||
concat_var(valueName, std::move(data));
|
||||
}
|
||||
|
||||
@@ -28,14 +28,43 @@ namespace til
|
||||
// A basic, hashmap with linear probing. A `LoadFactor` of 2 equals
|
||||
// a max. load of roughly 50% and a `LoadFactor` of 4 roughly 25%.
|
||||
//
|
||||
// `GrowthExponent` controls how fast the set grows and corresponds to
|
||||
// a rate of 2^GrowthExponent. In other words, a `GrowthExponent` of 3
|
||||
// equals a growth rate of 8x every time the capacity has been reached.
|
||||
//
|
||||
// It performs best with:
|
||||
// * small and cheap T
|
||||
// * >= 50% successful lookups
|
||||
// * <= 50% load factor (LoadFactor >= 2, which is the minimum anyways)
|
||||
template<typename T, size_t LoadFactor = 2>
|
||||
template<typename T, size_t LoadFactor = 2, size_t GrowthExponent = 1>
|
||||
struct linear_flat_set
|
||||
{
|
||||
static_assert(LoadFactor >= 2);
|
||||
static_assert(GrowthExponent >= 1);
|
||||
|
||||
linear_flat_set() = default;
|
||||
|
||||
linear_flat_set(const linear_flat_set&) = delete;
|
||||
linear_flat_set& operator=(const linear_flat_set&) = delete;
|
||||
|
||||
linear_flat_set(linear_flat_set&& other) noexcept :
|
||||
_map{ std::move(other._map) },
|
||||
_capacity{ std::exchange(other._capacity, 0) },
|
||||
_load{ std::exchange(other._load, 0) },
|
||||
_shift{ std::exchange(other._shift, initialShift) },
|
||||
_mask{ std::exchange(other._mask, 0) }
|
||||
{
|
||||
}
|
||||
|
||||
linear_flat_set& operator=(linear_flat_set&& other) noexcept
|
||||
{
|
||||
_map = std::move(other._map);
|
||||
_capacity = std::exchange(other._capacity, 0);
|
||||
_load = std::exchange(other._load, 0);
|
||||
_shift = std::exchange(other._shift, initialShift);
|
||||
_mask = std::exchange(other._mask, 0);
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool empty() const noexcept
|
||||
{
|
||||
@@ -52,6 +81,39 @@ namespace til
|
||||
return { _map.get(), _capacity };
|
||||
}
|
||||
|
||||
void clear() noexcept
|
||||
{
|
||||
if (_map)
|
||||
{
|
||||
std::fill_n(_map.get(), _capacity, T{});
|
||||
_load = 0;
|
||||
}
|
||||
}
|
||||
|
||||
template<typename U>
|
||||
T* lookup(U&& key) const noexcept
|
||||
{
|
||||
if (!_map)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const auto hash = ::std::hash<T>{}(key) >> _shift;
|
||||
|
||||
for (auto i = hash;; ++i)
|
||||
{
|
||||
auto& slot = _map[i & _mask];
|
||||
if (!slot)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
if (slot == key) [[likely]]
|
||||
{
|
||||
return &slot;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template<typename U>
|
||||
std::pair<T&, bool> insert(U&& key)
|
||||
{
|
||||
@@ -88,14 +150,15 @@ namespace til
|
||||
private:
|
||||
__declspec(noinline) void _bumpSize()
|
||||
{
|
||||
// For instance at a GrowthExponent of 1:
|
||||
// A _shift of 0 would result in a newShift of 0xfffff...
|
||||
// A _shift of 1 would result in a newCapacity of 0
|
||||
if (_shift < 2)
|
||||
if (_shift <= GrowthExponent)
|
||||
{
|
||||
throw std::bad_array_new_length{};
|
||||
}
|
||||
|
||||
const auto newShift = _shift - 1;
|
||||
const auto newShift = _shift - GrowthExponent;
|
||||
const auto newCapacity = size_t{ 1 } << (digits - newShift);
|
||||
const auto newMask = newCapacity - 1;
|
||||
auto newMap = std::make_unique<T[]>(newCapacity);
|
||||
@@ -128,12 +191,13 @@ namespace til
|
||||
}
|
||||
|
||||
static constexpr auto digits = std::numeric_limits<size_t>::digits;
|
||||
// This results in an initial capacity of 8 items, independent of the LoadFactor.
|
||||
static constexpr auto initialShift = digits - LoadFactor - 1;
|
||||
|
||||
std::unique_ptr<T[]> _map;
|
||||
size_t _capacity = 0;
|
||||
size_t _load = 0;
|
||||
// This results in an initial capacity of 8 items, independent of the LoadFactor.
|
||||
size_t _shift = digits - LoadFactor - 1;
|
||||
size_t _shift = initialShift;
|
||||
size_t _mask = 0;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -251,7 +251,8 @@ void Clipboard::StoreSelectionToClipboard(const bool copyFormatting)
|
||||
const auto text = buffer.GetText(includeCRLF,
|
||||
trimTrailingWhitespace,
|
||||
selectionRects,
|
||||
GetAttributeColors);
|
||||
GetAttributeColors,
|
||||
selection.IsKeyboardMarkSelection());
|
||||
|
||||
CopyTextToSystemClipboard(text, copyFormatting);
|
||||
}
|
||||
|
||||
@@ -648,8 +648,11 @@ void AtlasEngine::_resolveFontMetrics(const wchar_t* requestedFaceName, const Fo
|
||||
auto advanceWidth = 0.5f * fontSizeInPx;
|
||||
{
|
||||
static constexpr u32 codePoint = '0';
|
||||
|
||||
u16 glyphIndex;
|
||||
if (SUCCEEDED(fontFace->GetGlyphIndicesW(&codePoint, 1, &glyphIndex)))
|
||||
THROW_IF_FAILED(fontFace->GetGlyphIndicesW(&codePoint, 1, &glyphIndex));
|
||||
|
||||
if (glyphIndex)
|
||||
{
|
||||
DWRITE_GLYPH_METRICS glyphMetrics{};
|
||||
THROW_IF_FAILED(fontFace->GetDesignGlyphMetrics(&glyphIndex, 1, &glyphMetrics, FALSE));
|
||||
|
||||
@@ -134,12 +134,12 @@ try
|
||||
// the contents of the entire swap chain is redundant, but more importantly because the scroll rect
|
||||
// is the subset of the contents that are being scrolled into. If you scroll the entire viewport
|
||||
// then the scroll rect is empty, which Present1() will loudly complain about.
|
||||
if (_api.invalidatedRows == range<u16>{ 0, _p.s->cellCount.y })
|
||||
if (_p.invalidatedRows == range<u16>{ 0, _p.s->cellCount.y })
|
||||
{
|
||||
_p.MarkAllAsDirty();
|
||||
}
|
||||
|
||||
if (const auto offset = _api.scrollOffset)
|
||||
if (const auto offset = _p.scrollOffset)
|
||||
{
|
||||
if (offset < 0)
|
||||
{
|
||||
@@ -212,17 +212,17 @@ try
|
||||
// * Get the old dirty rect and mark that region as needing invalidation during the upcoming Present1(),
|
||||
// because it'll now be replaced with something else (for instance nothing/whitespace).
|
||||
// * Clear() them to prepare them for the new incoming content from the TextBuffer.
|
||||
if (_api.invalidatedRows.non_empty())
|
||||
if (_p.invalidatedRows.non_empty())
|
||||
{
|
||||
const til::CoordType targetSizeX = _p.s->targetSize.x;
|
||||
const til::CoordType targetSizeY = _p.s->targetSize.y;
|
||||
|
||||
_p.dirtyRectInPx.left = 0;
|
||||
_p.dirtyRectInPx.top = std::min(_p.dirtyRectInPx.top, _api.invalidatedRows.start * _p.s->font->cellSize.y);
|
||||
_p.dirtyRectInPx.top = std::min(_p.dirtyRectInPx.top, _p.invalidatedRows.start * _p.s->font->cellSize.y);
|
||||
_p.dirtyRectInPx.right = targetSizeX;
|
||||
_p.dirtyRectInPx.bottom = std::max(_p.dirtyRectInPx.bottom, _api.invalidatedRows.end * _p.s->font->cellSize.y);
|
||||
_p.dirtyRectInPx.bottom = std::max(_p.dirtyRectInPx.bottom, _p.invalidatedRows.end * _p.s->font->cellSize.y);
|
||||
|
||||
for (auto y = _api.invalidatedRows.start; y < _api.invalidatedRows.end; ++y)
|
||||
for (auto y = _p.invalidatedRows.start; y < _p.invalidatedRows.end; ++y)
|
||||
{
|
||||
const auto r = _p.rows[y];
|
||||
const auto clampedTop = clamp(r->dirtyTop, 0, targetSizeY);
|
||||
@@ -705,7 +705,7 @@ void AtlasEngine::_flushBufferLine()
|
||||
|
||||
void AtlasEngine::_mapCharacters(const wchar_t* text, const u32 textLength, u32* mappedLength, IDWriteFontFace2** mappedFontFace) const
|
||||
{
|
||||
TextAnalysisSource analysisSource{ text, textLength };
|
||||
TextAnalysisSource analysisSource{ _api.userLocaleName.c_str(), text, textLength };
|
||||
const auto& textFormatAxis = _api.textFormatAxes[static_cast<size_t>(_api.attributes)];
|
||||
|
||||
// We don't read from scale anyways.
|
||||
@@ -760,7 +760,7 @@ void AtlasEngine::_mapComplex(IDWriteFontFace2* mappedFontFace, u32 idx, u32 len
|
||||
{
|
||||
_api.analysisResults.clear();
|
||||
|
||||
TextAnalysisSource analysisSource{ _api.bufferLine.data(), gsl::narrow<UINT32>(_api.bufferLine.size()) };
|
||||
TextAnalysisSource analysisSource{ _api.userLocaleName.c_str(), _api.bufferLine.data(), gsl::narrow<UINT32>(_api.bufferLine.size()) };
|
||||
TextAnalysisSink analysisSink{ _api.analysisResults };
|
||||
THROW_IF_FAILED(_p.textAnalyzer->AnalyzeScript(&analysisSource, idx, length, &analysisSink));
|
||||
|
||||
|
||||
@@ -99,6 +99,7 @@ namespace Microsoft::Console::Render::Atlas
|
||||
ATLAS_ATTR_COLD void _recreateBackend();
|
||||
ATLAS_ATTR_COLD void _handleSwapChainUpdate();
|
||||
void _createSwapChain();
|
||||
void _destroySwapChain();
|
||||
void _resizeBuffers();
|
||||
void _updateMatrixTransform();
|
||||
void _waitUntilCanRender() noexcept;
|
||||
|
||||
@@ -32,11 +32,6 @@ using namespace Microsoft::Console::Render::Atlas;
|
||||
[[nodiscard]] HRESULT AtlasEngine::Present() noexcept
|
||||
try
|
||||
{
|
||||
if (!_p.dirtyRectInPx)
|
||||
{
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
if (!_p.dxgi.adapter || !_p.dxgi.factory->IsCurrent())
|
||||
{
|
||||
_recreateAdapter();
|
||||
@@ -167,6 +162,10 @@ void AtlasEngine::_recreateAdapter()
|
||||
|
||||
void AtlasEngine::_recreateBackend()
|
||||
{
|
||||
// D3D11 defers the destruction of objects and only one swap chain can be associated with a
|
||||
// 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 deviceFlags =
|
||||
D3D11_CREATE_DEVICE_SINGLETHREADED
|
||||
@@ -265,7 +264,6 @@ void AtlasEngine::_recreateBackend()
|
||||
d2dMode |= !options.ComputeShaders_Plus_RawAndStructuredBuffers_Via_Shader_4_x;
|
||||
}
|
||||
|
||||
_p.swapChain = {};
|
||||
_p.device = std::move(device);
|
||||
_p.deviceContext = std::move(deviceContext);
|
||||
|
||||
@@ -290,18 +288,10 @@ void AtlasEngine::_handleSwapChainUpdate()
|
||||
{
|
||||
if (_p.swapChain.targetGeneration != _p.s->target.generation())
|
||||
{
|
||||
if (_p.swapChain.swapChain)
|
||||
{
|
||||
_b->ReleaseResources();
|
||||
_p.deviceContext->ClearState();
|
||||
_p.deviceContext->Flush();
|
||||
}
|
||||
_createSwapChain();
|
||||
}
|
||||
else if (_p.swapChain.targetSize != _p.s->targetSize)
|
||||
{
|
||||
_b->ReleaseResources();
|
||||
_p.deviceContext->ClearState();
|
||||
_resizeBuffers();
|
||||
}
|
||||
|
||||
@@ -317,8 +307,7 @@ static constexpr DXGI_SWAP_CHAIN_FLAG swapChainFlags = ATLAS_DEBUG_DISABLE_FRAME
|
||||
|
||||
void AtlasEngine::_createSwapChain()
|
||||
{
|
||||
_p.swapChain.swapChain.reset();
|
||||
_p.swapChain.frameLatencyWaitableObject.reset();
|
||||
_destroySwapChain();
|
||||
|
||||
DXGI_SWAP_CHAIN_DESC1 desc{
|
||||
.Width = _p.s->targetSize.x,
|
||||
@@ -345,6 +334,7 @@ void AtlasEngine::_createSwapChain()
|
||||
};
|
||||
|
||||
wil::com_ptr<IDXGISwapChain1> swapChain1;
|
||||
wil::unique_handle handle;
|
||||
|
||||
if (_p.s->target->hwnd)
|
||||
{
|
||||
@@ -359,14 +349,14 @@ void AtlasEngine::_createSwapChain()
|
||||
|
||||
// As per: https://docs.microsoft.com/en-us/windows/win32/api/dcomp/nf-dcomp-dcompositioncreatesurfacehandle
|
||||
static constexpr DWORD COMPOSITIONSURFACE_ALL_ACCESS = 0x0003L;
|
||||
THROW_IF_FAILED(DCompositionCreateSurfaceHandle(COMPOSITIONSURFACE_ALL_ACCESS, nullptr, _p.swapChain.handle.addressof()));
|
||||
THROW_IF_FAILED(_p.dxgi.factory.query<IDXGIFactoryMedia>()->CreateSwapChainForCompositionSurfaceHandle(_p.device.get(), _p.swapChain.handle.get(), &desc, nullptr, swapChain1.addressof()));
|
||||
THROW_IF_FAILED(DCompositionCreateSurfaceHandle(COMPOSITIONSURFACE_ALL_ACCESS, nullptr, handle.addressof()));
|
||||
THROW_IF_FAILED(_p.dxgi.factory.query<IDXGIFactoryMedia>()->CreateSwapChainForCompositionSurfaceHandle(_p.device.get(), handle.get(), &desc, nullptr, swapChain1.addressof()));
|
||||
}
|
||||
|
||||
_p.swapChain.swapChain = swapChain1.query<IDXGISwapChain2>();
|
||||
_p.swapChain.handle = std::move(handle);
|
||||
_p.swapChain.frameLatencyWaitableObject.reset(_p.swapChain.swapChain->GetFrameLatencyWaitableObject());
|
||||
_p.swapChain.targetGeneration = _p.s->target.generation();
|
||||
_p.swapChain.fontGeneration = {};
|
||||
_p.swapChain.targetSize = _p.s->targetSize;
|
||||
_p.swapChain.waitForPresentation = true;
|
||||
|
||||
@@ -382,8 +372,30 @@ void AtlasEngine::_createSwapChain()
|
||||
}
|
||||
}
|
||||
|
||||
void AtlasEngine::_destroySwapChain()
|
||||
{
|
||||
if (_p.swapChain.swapChain)
|
||||
{
|
||||
// D3D11 defers the destruction of objects and only one swap chain can be associated with a
|
||||
// HWND, IWindow, or composition surface at a time. --> Force the destruction of all objects.
|
||||
_p.swapChain = {};
|
||||
if (_b)
|
||||
{
|
||||
_b->ReleaseResources();
|
||||
}
|
||||
if (_p.deviceContext)
|
||||
{
|
||||
_p.deviceContext->ClearState();
|
||||
_p.deviceContext->Flush();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AtlasEngine::_resizeBuffers()
|
||||
{
|
||||
_b->ReleaseResources();
|
||||
_p.deviceContext->ClearState();
|
||||
|
||||
THROW_IF_FAILED(_p.swapChain.swapChain->ResizeBuffers(0, _p.s->targetSize.x, _p.s->targetSize.y, DXGI_FORMAT_UNKNOWN, swapChainFlags));
|
||||
_p.swapChain.targetSize = _p.s->targetSize;
|
||||
}
|
||||
@@ -420,25 +432,32 @@ void AtlasEngine::_waitUntilCanRender() noexcept
|
||||
|
||||
void AtlasEngine::_present()
|
||||
{
|
||||
const til::rect fullRect{ 0, 0, _p.swapChain.targetSize.x, _p.swapChain.targetSize.y };
|
||||
const RECT fullRect{ 0, 0, _p.swapChain.targetSize.x, _p.swapChain.targetSize.y };
|
||||
|
||||
DXGI_PRESENT_PARAMETERS params{};
|
||||
RECT scrollRect{};
|
||||
POINT scrollOffset{};
|
||||
|
||||
// Since rows might be taller than their cells, they might have drawn outside of the viewport.
|
||||
auto dirtyRect = _p.dirtyRectInPx;
|
||||
dirtyRect.left = std::max(dirtyRect.left, 0);
|
||||
dirtyRect.top = std::max(dirtyRect.top, 0);
|
||||
dirtyRect.right = std::min(dirtyRect.right, fullRect.right);
|
||||
dirtyRect.bottom = std::min(dirtyRect.bottom, fullRect.bottom);
|
||||
RECT dirtyRect{
|
||||
.left = std::max(_p.dirtyRectInPx.left, 0),
|
||||
.top = std::max(_p.dirtyRectInPx.top, 0),
|
||||
.right = std::min<LONG>(_p.dirtyRectInPx.right, fullRect.right),
|
||||
.bottom = std::min<LONG>(_p.dirtyRectInPx.bottom, fullRect.bottom),
|
||||
};
|
||||
|
||||
// Present1() dislikes being called with an empty dirty rect.
|
||||
if (dirtyRect.left >= dirtyRect.right || dirtyRect.top >= dirtyRect.bottom)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if constexpr (!ATLAS_DEBUG_SHOW_DIRTY)
|
||||
{
|
||||
if (dirtyRect != fullRect)
|
||||
if (memcmp(&dirtyRect, &fullRect, sizeof(RECT)) != 0)
|
||||
{
|
||||
params.DirtyRectsCount = 1;
|
||||
params.pDirtyRects = dirtyRect.as_win32_rect();
|
||||
params.pDirtyRects = &dirtyRect;
|
||||
|
||||
if (_p.scrollOffset)
|
||||
{
|
||||
|
||||
@@ -4,8 +4,6 @@
|
||||
#include "pch.h"
|
||||
#include "Backend.h"
|
||||
|
||||
#include <dwmapi.h>
|
||||
|
||||
TIL_FAST_MATH_BEGIN
|
||||
|
||||
// Disable a bunch of warnings which get in the way of writing performant code.
|
||||
|
||||
@@ -538,6 +538,7 @@ void BackendD2D::_resizeCursorBitmap(const RenderingPayload& p, const til::size
|
||||
cursorRenderTarget->SetAntialiasMode(D2D1_ANTIALIAS_MODE_ALIASED);
|
||||
|
||||
cursorRenderTarget->BeginDraw();
|
||||
cursorRenderTarget->Clear();
|
||||
{
|
||||
const D2D1_RECT_F rect{ 0, 0, sizeF.width, sizeF.height };
|
||||
const auto brush = _brushWithColor(0xffffffff);
|
||||
@@ -627,7 +628,8 @@ void BackendD2D::_debugShowDirty(const RenderingPayload& p)
|
||||
|
||||
for (size_t i = 0; i < std::size(_presentRects); ++i)
|
||||
{
|
||||
if (const auto& rect = _presentRects[i])
|
||||
const auto& rect = _presentRects[(_presentRectsPos + i) % std::size(_presentRects)];
|
||||
if (rect.non_empty())
|
||||
{
|
||||
const D2D1_RECT_F rectF{
|
||||
static_cast<f32>(rect.left),
|
||||
|
||||
@@ -56,7 +56,7 @@ namespace Microsoft::Console::Render::Atlas
|
||||
u16x2 _cellCount{};
|
||||
|
||||
#if ATLAS_DEBUG_SHOW_DIRTY
|
||||
til::rect _presentRects[9]{};
|
||||
i32r _presentRects[9]{};
|
||||
size_t _presentRectsPos = 0;
|
||||
#endif
|
||||
|
||||
|
||||
@@ -36,10 +36,23 @@ TIL_FAST_MATH_BEGIN
|
||||
#pragma warning(disable : 26481) // Don't use pointer arithmetic. Use span instead (bounds.1).
|
||||
#pragma warning(disable : 26482) // Only index into arrays using constant expressions (bounds.2).
|
||||
|
||||
// Initializing large arrays can be very costly compared to how cheap some of these functions are.
|
||||
#define ALLOW_UNINITIALIZED_BEGIN _Pragma("warning(push)") _Pragma("warning(disable : 26494)")
|
||||
#define ALLOW_UNINITIALIZED_END _Pragma("warning(pop)")
|
||||
|
||||
using namespace Microsoft::Console::Render::Atlas;
|
||||
|
||||
template<>
|
||||
struct ::std::hash<BackendD3D::AtlasGlyphEntry>
|
||||
struct std::hash<u16>
|
||||
{
|
||||
constexpr size_t operator()(u16 key) const noexcept
|
||||
{
|
||||
return til::flat_set_hash_integer(key);
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
struct std::hash<BackendD3D::AtlasGlyphEntry>
|
||||
{
|
||||
constexpr size_t operator()(u16 key) const noexcept
|
||||
{
|
||||
@@ -53,7 +66,7 @@ struct ::std::hash<BackendD3D::AtlasGlyphEntry>
|
||||
};
|
||||
|
||||
template<>
|
||||
struct ::std::hash<BackendD3D::AtlasFontFaceEntry>
|
||||
struct std::hash<BackendD3D::AtlasFontFaceEntry>
|
||||
{
|
||||
using T = BackendD3D::AtlasFontFaceEntry;
|
||||
|
||||
@@ -707,9 +720,16 @@ void BackendD3D::_resetGlyphAtlas(const RenderingPayload& p)
|
||||
|
||||
stbrp_init_target(&_rectPacker, u, v, _rectPackerData.data(), _rectPackerData.size());
|
||||
|
||||
// This is a little imperfect, because it only releases the memory of the glyph mappings, not the memory held by
|
||||
// any DirectWrite fonts. On the other side, the amount of fonts on a system is always finite, where "finite"
|
||||
// is pretty low, relatively speaking. Additionally this allows us to cache the boxGlyphs map indefinitely.
|
||||
// It's not great, but it's not terrible.
|
||||
for (auto& slot : _glyphAtlasMap.container())
|
||||
{
|
||||
slot.inner.reset();
|
||||
if (slot.inner)
|
||||
{
|
||||
slot.inner->glyphs.clear();
|
||||
}
|
||||
}
|
||||
|
||||
_d2dBeginDrawing();
|
||||
@@ -975,7 +995,13 @@ void BackendD3D::_drawText(RenderingPayload& p)
|
||||
// We need to goto here, because a retry will cause the atlas texture as well as the
|
||||
// _glyphCache hashmap to be cleared, and so we'll have to call insert() again.
|
||||
drawGlyphRetry:
|
||||
auto& fontFaceEntry = *_glyphAtlasMap.insert(fontFaceKey).first.inner;
|
||||
const auto [fontFaceEntryOuter, fontFaceInserted] = _glyphAtlasMap.insert(fontFaceKey);
|
||||
auto& fontFaceEntry = *fontFaceEntryOuter.inner;
|
||||
|
||||
if (fontFaceInserted)
|
||||
{
|
||||
_initializeFontFaceEntry(fontFaceEntry);
|
||||
}
|
||||
|
||||
while (x < m.glyphsTo)
|
||||
{
|
||||
@@ -1117,7 +1143,35 @@ void BackendD3D::_drawTextOverlapSplit(const RenderingPayload& p, u16 y)
|
||||
}
|
||||
}
|
||||
|
||||
bool BackendD3D::_drawGlyph(const RenderingPayload& p, const BackendD3D::AtlasFontFaceEntryInner& fontFaceEntry, BackendD3D::AtlasGlyphEntry& glyphEntry)
|
||||
void BackendD3D::_initializeFontFaceEntry(AtlasFontFaceEntryInner& fontFaceEntry)
|
||||
{
|
||||
if (!fontFaceEntry.fontFace)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ALLOW_UNINITIALIZED_BEGIN
|
||||
std::array<u32, 0x100> codepoints;
|
||||
std::array<u16, 0x100> indices;
|
||||
ALLOW_UNINITIALIZED_END
|
||||
|
||||
for (u32 i = 0; i < codepoints.size(); ++i)
|
||||
{
|
||||
codepoints[i] = 0x2500 + i;
|
||||
}
|
||||
|
||||
THROW_IF_FAILED(fontFaceEntry.fontFace->GetGlyphIndicesW(codepoints.data(), codepoints.size(), indices.data()));
|
||||
|
||||
for (u32 i = 0; i < indices.size(); ++i)
|
||||
{
|
||||
if (const auto idx = indices[i])
|
||||
{
|
||||
fontFaceEntry.boxGlyphs.insert(idx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool BackendD3D::_drawGlyph(const RenderingPayload& p, const AtlasFontFaceEntryInner& fontFaceEntry, AtlasGlyphEntry& glyphEntry)
|
||||
{
|
||||
if (!fontFaceEntry.fontFace)
|
||||
{
|
||||
@@ -1200,22 +1254,20 @@ bool BackendD3D::_drawGlyph(const RenderingPayload& p, const BackendD3D::AtlasFo
|
||||
#endif
|
||||
|
||||
const auto lineRendition = static_cast<LineRendition>(fontFaceEntry.lineRendition);
|
||||
std::optional<D2D1_MATRIX_3X2_F> transform;
|
||||
const auto needsTransform = lineRendition != LineRendition::SingleWidth;
|
||||
|
||||
if (lineRendition != LineRendition::SingleWidth)
|
||||
static constexpr D2D1_MATRIX_3X2_F identityTransform{ .m11 = 1, .m22 = 1 };
|
||||
D2D1_MATRIX_3X2_F transform = identityTransform;
|
||||
|
||||
if (needsTransform)
|
||||
{
|
||||
auto& t = transform.emplace();
|
||||
t.m11 = 2.0f;
|
||||
t.m22 = lineRendition >= LineRendition::DoubleHeightTop ? 2.0f : 1.0f;
|
||||
_d2dRenderTarget->SetTransform(&t);
|
||||
transform.m11 = 2.0f;
|
||||
transform.m22 = lineRendition >= LineRendition::DoubleHeightTop ? 2.0f : 1.0f;
|
||||
_d2dRenderTarget->SetTransform(&transform);
|
||||
}
|
||||
|
||||
const auto restoreTransform = wil::scope_exit([&]() noexcept {
|
||||
if (transform)
|
||||
{
|
||||
static constexpr D2D1_MATRIX_3X2_F identity{ .m11 = 1, .m22 = 1 };
|
||||
_d2dRenderTarget->SetTransform(&identity);
|
||||
}
|
||||
_d2dRenderTarget->SetTransform(&identityTransform);
|
||||
});
|
||||
|
||||
// This calculates the black box of the glyph, or in other words,
|
||||
@@ -1239,7 +1291,7 @@ bool BackendD3D::_drawGlyph(const RenderingPayload& p, const BackendD3D::AtlasFo
|
||||
bool isColorGlyph = false;
|
||||
D2D1_RECT_F bounds = GlyphRunEmptyBounds;
|
||||
|
||||
const auto cleanup = wil::scope_exit([&]() {
|
||||
const auto antialiasingCleanup = wil::scope_exit([&]() {
|
||||
if (isColorGlyph)
|
||||
{
|
||||
_d2dRenderTarget4->SetTextAntialiasMode(static_cast<D2D1_TEXT_ANTIALIAS_MODE>(p.s->font->antialiasingMode));
|
||||
@@ -1266,7 +1318,23 @@ bool BackendD3D::_drawGlyph(const RenderingPayload& p, const BackendD3D::AtlasFo
|
||||
}
|
||||
}
|
||||
|
||||
// box may be empty if the glyph is whitespace.
|
||||
// Overhangs for box glyphs can produce unsightly effects, where the antialiased edges of horizontal
|
||||
// and vertical lines overlap between neighboring glyphs and produce "boldened" intersections.
|
||||
// It looks a little something like this:
|
||||
// ---+---+---
|
||||
// This avoids the issue in most cases by simply clipping the glyph to the size of a single cell.
|
||||
// The downside is that it fails to work well for custom line heights, etc.
|
||||
const auto isBoxGlyph = fontFaceEntry.boxGlyphs.lookup(glyphEntry.glyphIndex) != nullptr;
|
||||
if (isBoxGlyph)
|
||||
{
|
||||
// NOTE: As mentioned above, the "origin" of a glyph's coordinate system is its baseline.
|
||||
bounds.left = std::max(bounds.left, 0.0f);
|
||||
bounds.top = std::max(bounds.top, static_cast<f32>(-p.s->font->baseline) * transform.m22);
|
||||
bounds.right = std::min(bounds.right, static_cast<f32>(p.s->font->cellSize.x) * transform.m11);
|
||||
bounds.bottom = std::min(bounds.bottom, static_cast<f32>(p.s->font->descender) * transform.m22);
|
||||
}
|
||||
|
||||
// The bounds may be empty if the glyph is whitespace.
|
||||
if (bounds.left >= bounds.right || bounds.top >= bounds.bottom)
|
||||
{
|
||||
return true;
|
||||
@@ -1292,16 +1360,32 @@ bool BackendD3D::_drawGlyph(const RenderingPayload& p, const BackendD3D::AtlasFo
|
||||
static_cast<f32>(rect.y - bt),
|
||||
};
|
||||
|
||||
if (transform)
|
||||
{
|
||||
auto& t = *transform;
|
||||
t.dx = (1.0f - t.m11) * baselineOrigin.x;
|
||||
t.dy = (1.0f - t.m22) * baselineOrigin.y;
|
||||
_d2dRenderTarget->SetTransform(&t);
|
||||
}
|
||||
|
||||
_d2dBeginDrawing();
|
||||
|
||||
if (isBoxGlyph)
|
||||
{
|
||||
const D2D1_RECT_F clipRect{
|
||||
static_cast<f32>(rect.x) / transform.m11,
|
||||
static_cast<f32>(rect.y) / transform.m22,
|
||||
static_cast<f32>(rect.x + rect.w) / transform.m11,
|
||||
static_cast<f32>(rect.y + rect.h) / transform.m22,
|
||||
};
|
||||
_d2dRenderTarget4->PushAxisAlignedClip(&clipRect, D2D1_ANTIALIAS_MODE_ALIASED);
|
||||
}
|
||||
const auto boxGlyphCleanup = wil::scope_exit([&]() {
|
||||
if (isBoxGlyph)
|
||||
{
|
||||
_d2dRenderTarget4->PopAxisAlignedClip();
|
||||
}
|
||||
});
|
||||
|
||||
if (needsTransform)
|
||||
{
|
||||
transform.dx = (1.0f - transform.m11) * baselineOrigin.x;
|
||||
transform.dy = (1.0f - transform.m22) * baselineOrigin.y;
|
||||
_d2dRenderTarget->SetTransform(&transform);
|
||||
}
|
||||
|
||||
if (!isColorGlyph)
|
||||
{
|
||||
_d2dRenderTarget->DrawGlyphRun(baselineOrigin, &glyphRun, _brush.get(), DWRITE_MEASURING_MODE_NATURAL);
|
||||
@@ -1963,10 +2047,11 @@ void BackendD3D::_debugShowDirty(const RenderingPayload& p)
|
||||
|
||||
for (size_t i = 0; i < std::size(_presentRects); ++i)
|
||||
{
|
||||
if (const auto& rect = _presentRects[i])
|
||||
const auto& rect = _presentRects[(_presentRectsPos + i) % std::size(_presentRects)];
|
||||
if (rect.non_empty())
|
||||
{
|
||||
_appendQuad() = {
|
||||
.shadingType = ShadingType::SolidFill,
|
||||
.shadingType = ShadingType::Selection,
|
||||
.position = {
|
||||
static_cast<i16>(rect.left),
|
||||
static_cast<i16>(rect.top),
|
||||
|
||||
@@ -151,6 +151,9 @@ namespace Microsoft::Console::Render::Atlas
|
||||
LineRendition lineRendition = LineRendition::SingleWidth;
|
||||
|
||||
til::linear_flat_set<AtlasGlyphEntry> glyphs;
|
||||
// boxGlyphs gets an increased growth rate of 2^2 = 4x, because presumably fonts either contain very
|
||||
// few or almost all of the box glyphs. This reduces the cost of _initializeFontFaceEntry quite a bit.
|
||||
til::linear_flat_set<u16, 2, 2> boxGlyphs;
|
||||
};
|
||||
|
||||
struct AtlasFontFaceEntry
|
||||
@@ -214,6 +217,7 @@ namespace Microsoft::Console::Render::Atlas
|
||||
void _uploadBackgroundBitmap(const RenderingPayload& p);
|
||||
void _drawText(RenderingPayload& p);
|
||||
ATLAS_ATTR_COLD void _drawTextOverlapSplit(const RenderingPayload& p, u16 y);
|
||||
ATLAS_ATTR_COLD static void _initializeFontFaceEntry(AtlasFontFaceEntryInner& fontFaceEntry);
|
||||
ATLAS_ATTR_COLD [[nodiscard]] bool _drawGlyph(const RenderingPayload& p, const AtlasFontFaceEntryInner& fontFaceEntry, AtlasGlyphEntry& glyphEntry);
|
||||
bool _drawSoftFontGlyph(const RenderingPayload& p, const AtlasFontFaceEntryInner& fontFaceEntry, AtlasGlyphEntry& glyphEntry);
|
||||
void _drawGlyphPrepareRetry(const RenderingPayload& p);
|
||||
@@ -289,7 +293,7 @@ namespace Microsoft::Console::Render::Atlas
|
||||
bool _requiresContinuousRedraw = false;
|
||||
|
||||
#if ATLAS_DEBUG_SHOW_DIRTY
|
||||
til::rect _presentRects[9]{};
|
||||
i32r _presentRects[9]{};
|
||||
size_t _presentRectsPos = 0;
|
||||
#endif
|
||||
|
||||
|
||||
@@ -9,9 +9,10 @@
|
||||
|
||||
using namespace Microsoft::Console::Render::Atlas;
|
||||
|
||||
TextAnalysisSource::TextAnalysisSource(const wchar_t* _text, const UINT32 _textLength) noexcept :
|
||||
_text{ _text },
|
||||
_textLength{ _textLength }
|
||||
TextAnalysisSource::TextAnalysisSource(const wchar_t* locale, const wchar_t* text, const UINT32 textLength) noexcept :
|
||||
_locale{ locale },
|
||||
_text{ text },
|
||||
_textLength{ textLength }
|
||||
{
|
||||
}
|
||||
|
||||
@@ -94,7 +95,7 @@ HRESULT TextAnalysisSource::GetLocaleName(UINT32 textPosition, UINT32* textLengt
|
||||
__assume(localeName != nullptr);
|
||||
|
||||
*textLength = _textLength - textPosition;
|
||||
*localeName = nullptr;
|
||||
*localeName = _locale;
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ namespace Microsoft::Console::Render::Atlas
|
||||
{
|
||||
struct TextAnalysisSource final : IDWriteTextAnalysisSource
|
||||
{
|
||||
TextAnalysisSource(const wchar_t* _text, const UINT32 _textLength) noexcept;
|
||||
TextAnalysisSource(const wchar_t* locale, const wchar_t* text, const UINT32 textLength) noexcept;
|
||||
#ifndef NDEBUG
|
||||
~TextAnalysisSource();
|
||||
#endif
|
||||
@@ -24,6 +24,7 @@ namespace Microsoft::Console::Render::Atlas
|
||||
HRESULT __stdcall GetNumberSubstitution(UINT32 textPosition, UINT32* textLength, IDWriteNumberSubstitution** numberSubstitution) noexcept override;
|
||||
|
||||
private:
|
||||
const wchar_t* _locale;
|
||||
const wchar_t* _text;
|
||||
const UINT32 _textLength;
|
||||
#ifndef NDEBUG
|
||||
|
||||
@@ -531,8 +531,12 @@ namespace Microsoft::Console::Render::Atlas
|
||||
std::array<til::generation_t, 2> colorBitmapGenerations{ 1, 1 };
|
||||
// In columns/rows.
|
||||
til::rect cursorRect;
|
||||
// In pixel.
|
||||
til::rect dirtyRectInPx;
|
||||
// The viewport/SwapChain area to be presented. In pixel.
|
||||
// NOTE:
|
||||
// This cannot use til::rect, because til::rect generally expects positive coordinates only
|
||||
// (`operator!()` checks for negative values), whereas this one can go out of bounds,
|
||||
// whenever glyphs go out of bounds. `AtlasEngine::_present()` will clamp it.
|
||||
i32r dirtyRectInPx{};
|
||||
// In rows.
|
||||
range<u16> invalidatedRows{};
|
||||
// In pixel.
|
||||
@@ -553,5 +557,4 @@ namespace Microsoft::Console::Render::Atlas
|
||||
virtual void Render(RenderingPayload& payload) = 0;
|
||||
virtual bool RequiresContinuousRedraw() noexcept = 0;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -418,6 +418,7 @@ namespace Microsoft::Console::VirtualTerminal::DispatchTypes
|
||||
enum ModeParams : VTInt
|
||||
{
|
||||
IRM_InsertReplaceMode = ANSIStandardMode(4),
|
||||
LNM_LineFeedNewLineMode = ANSIStandardMode(20),
|
||||
DECCKM_CursorKeysMode = DECPrivateMode(1),
|
||||
DECANM_AnsiMode = DECPrivateMode(2),
|
||||
DECCOLM_SetNumberOfColumns = DECPrivateMode(3),
|
||||
@@ -430,6 +431,7 @@ namespace Microsoft::Console::VirtualTerminal::DispatchTypes
|
||||
XTERM_EnableDECCOLMSupport = DECPrivateMode(40),
|
||||
DECNKM_NumericKeypadMode = DECPrivateMode(66),
|
||||
DECBKM_BackarrowKeyMode = DECPrivateMode(67),
|
||||
DECLRMM_LeftRightMarginMode = DECPrivateMode(69),
|
||||
VT200_MOUSE_MODE = DECPrivateMode(1000),
|
||||
BUTTON_EVENT_MOUSE_MODE = DECPrivateMode(1002),
|
||||
ANY_EVENT_MOUSE_MODE = DECPrivateMode(1003),
|
||||
|
||||
@@ -54,6 +54,7 @@ public:
|
||||
virtual bool SetKeypadMode(const bool applicationMode) = 0; // DECKPAM, DECKPNM
|
||||
virtual bool SetAnsiMode(const bool ansiMode) = 0; // DECANM
|
||||
virtual bool SetTopBottomScrollingMargins(const VTInt topMargin, const VTInt bottomMargin) = 0; // DECSTBM
|
||||
virtual bool SetLeftRightScrollingMargins(const VTInt leftMargin, const VTInt rightMargin) = 0; // DECSLRM
|
||||
virtual bool WarningBell() = 0; // BEL
|
||||
virtual bool CarriageReturn() = 0; // CR
|
||||
virtual bool LineFeed(const DispatchTypes::LineFeedType lineFeedType) = 0; // IND, NEL, LF, FF, VT
|
||||
|
||||
@@ -48,11 +48,17 @@ namespace Microsoft::Console::VirtualTerminal
|
||||
|
||||
virtual void SetTextAttributes(const TextAttribute& attrs) = 0;
|
||||
|
||||
virtual void SetAutoWrapMode(const bool wrapAtEOL) = 0;
|
||||
virtual bool GetAutoWrapMode() const = 0;
|
||||
enum class Mode : size_t
|
||||
{
|
||||
AutoWrap,
|
||||
LineFeed,
|
||||
BracketedPaste
|
||||
};
|
||||
|
||||
virtual void SetSystemMode(const Mode mode, const bool enabled) = 0;
|
||||
virtual bool GetSystemMode(const Mode mode) const = 0;
|
||||
|
||||
virtual void WarningBell() = 0;
|
||||
virtual bool GetLineFeedMode() const = 0;
|
||||
virtual void SetWindowTitle(const std::wstring_view title) = 0;
|
||||
virtual void UseAlternateScreenBuffer() = 0;
|
||||
virtual void UseMainScreenBuffer() = 0;
|
||||
@@ -64,8 +70,6 @@ namespace Microsoft::Console::VirtualTerminal
|
||||
virtual void SetConsoleOutputCP(const unsigned int codepage) = 0;
|
||||
virtual unsigned int GetConsoleOutputCP() const = 0;
|
||||
|
||||
virtual void SetBracketedPasteMode(const bool enabled) = 0;
|
||||
virtual bool GetBracketedPasteMode() const = 0;
|
||||
virtual void CopyToClipboard(const std::wstring_view content) = 0;
|
||||
virtual void SetTaskbarProgress(const DispatchTypes::TaskbarState state, const size_t progress) = 0;
|
||||
virtual void SetWorkingDirectory(const std::wstring_view uri) = 0;
|
||||
|
||||
@@ -74,15 +74,25 @@ void AdaptDispatch::_WriteToBuffer(const std::wstring_view string)
|
||||
auto& textBuffer = _api.GetTextBuffer();
|
||||
auto& cursor = textBuffer.GetCursor();
|
||||
auto cursorPosition = cursor.GetPosition();
|
||||
const auto wrapAtEOL = _api.GetAutoWrapMode();
|
||||
const auto wrapAtEOL = _api.GetSystemMode(ITerminalApi::Mode::AutoWrap);
|
||||
const auto attributes = textBuffer.GetCurrentAttributes();
|
||||
|
||||
const auto viewport = _api.GetViewport();
|
||||
const auto [topMargin, bottomMargin] = _GetVerticalMargins(viewport, true);
|
||||
const auto [leftMargin, rightMargin] = _GetHorizontalMargins(textBuffer.GetSize().Width());
|
||||
|
||||
auto lineWidth = textBuffer.GetLineWidth(cursorPosition.y);
|
||||
if (cursorPosition.x <= rightMargin && cursorPosition.y >= topMargin && cursorPosition.y <= bottomMargin)
|
||||
{
|
||||
lineWidth = std::min(lineWidth, rightMargin + 1);
|
||||
}
|
||||
|
||||
// Turn off the cursor until we're done, so it isn't refreshed unnecessarily.
|
||||
cursor.SetIsOn(false);
|
||||
|
||||
RowWriteState state{
|
||||
.text = string,
|
||||
.columnLimit = textBuffer.GetLineWidth(cursorPosition.y),
|
||||
.columnLimit = lineWidth,
|
||||
};
|
||||
|
||||
while (!state.text.empty())
|
||||
@@ -98,7 +108,12 @@ void AdaptDispatch::_WriteToBuffer(const std::wstring_view string)
|
||||
_DoLineFeed(textBuffer, true, true);
|
||||
cursorPosition = cursor.GetPosition();
|
||||
// We need to recalculate the width when moving to a new line.
|
||||
state.columnLimit = textBuffer.GetLineWidth(cursorPosition.y);
|
||||
lineWidth = textBuffer.GetLineWidth(cursorPosition.y);
|
||||
if (cursorPosition.y >= topMargin && cursorPosition.y <= bottomMargin)
|
||||
{
|
||||
lineWidth = std::min(lineWidth, rightMargin + 1);
|
||||
}
|
||||
state.columnLimit = lineWidth;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -278,6 +293,29 @@ std::pair<int, int> AdaptDispatch::_GetVerticalMargins(const til::rect& viewport
|
||||
return { topMargin, bottomMargin };
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Returns the coordinates of the horizontal scroll margins.
|
||||
// Arguments:
|
||||
// - bufferWidth - The width of the buffer
|
||||
// Return Value:
|
||||
// - A std::pair containing the left and right coordinates (inclusive).
|
||||
std::pair<int, int> AdaptDispatch::_GetHorizontalMargins(const til::CoordType bufferWidth) noexcept
|
||||
{
|
||||
// If the left is out of range, reset the margins completely.
|
||||
const auto rightmostColumn = bufferWidth - 1;
|
||||
if (_scrollMargins.left >= rightmostColumn)
|
||||
{
|
||||
_scrollMargins.left = _scrollMargins.right = 0;
|
||||
}
|
||||
// If margins aren't set, use the full extent of the buffer.
|
||||
const auto marginsSet = _scrollMargins.left < _scrollMargins.right;
|
||||
auto leftMargin = marginsSet ? _scrollMargins.left : 0;
|
||||
auto rightMargin = marginsSet ? _scrollMargins.right : rightmostColumn;
|
||||
// If the right is out of range, clamp it to the rightmost column.
|
||||
rightMargin = std::min(rightMargin, rightmostColumn);
|
||||
return { leftMargin, rightMargin };
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Generalizes cursor movement to a specific position, which can be absolute or relative.
|
||||
// Arguments:
|
||||
@@ -292,8 +330,10 @@ bool AdaptDispatch::_CursorMovePosition(const Offset rowOffset, const Offset col
|
||||
const auto viewport = _api.GetViewport();
|
||||
auto& textBuffer = _api.GetTextBuffer();
|
||||
auto& cursor = textBuffer.GetCursor();
|
||||
const auto bufferWidth = textBuffer.GetSize().Width();
|
||||
const auto cursorPosition = cursor.GetPosition();
|
||||
const auto [topMargin, bottomMargin] = _GetVerticalMargins(viewport, true);
|
||||
const auto [leftMargin, rightMargin] = _GetHorizontalMargins(bufferWidth);
|
||||
|
||||
// For relative movement, the given offsets will be relative to
|
||||
// the current cursor position.
|
||||
@@ -307,39 +347,54 @@ bool AdaptDispatch::_CursorMovePosition(const Offset rowOffset, const Offset col
|
||||
row = _modes.test(Mode::Origin) ? topMargin : viewport.top;
|
||||
}
|
||||
|
||||
// And if the column is absolute, it'll be relative to column 0.
|
||||
// And if the column is absolute, it'll be relative to column 0,
|
||||
// or the left margin, depending on the origin mode.
|
||||
// Horizontal positions are not affected by the viewport.
|
||||
if (colOffset.IsAbsolute)
|
||||
{
|
||||
col = 0;
|
||||
col = _modes.test(Mode::Origin) ? leftMargin : 0;
|
||||
}
|
||||
|
||||
// Adjust the base position by the given offsets and clamp the results.
|
||||
// The row is constrained within the viewport's vertical boundaries,
|
||||
// while the column is constrained by the buffer width.
|
||||
row = std::clamp(row + rowOffset.Value, viewport.top, viewport.bottom - 1);
|
||||
col = std::clamp(col + colOffset.Value, 0, textBuffer.GetSize().Width() - 1);
|
||||
col = std::clamp(col + colOffset.Value, 0, bufferWidth - 1);
|
||||
|
||||
// If the operation needs to be clamped inside the margins, or the origin
|
||||
// mode is relative (which always requires margin clamping), then the row
|
||||
// may need to be adjusted further.
|
||||
// and column may need to be adjusted further.
|
||||
if (clampInMargins || _modes.test(Mode::Origin))
|
||||
{
|
||||
// See microsoft/terminal#2929 - If the cursor is _below_ the top
|
||||
// margin, it should stay below the top margin. If it's _above_ the
|
||||
// bottom, it should stay above the bottom. Cursor movements that stay
|
||||
// outside the margins shouldn't necessarily be affected. For example,
|
||||
// moving up while below the bottom margin shouldn't just jump straight
|
||||
// to the bottom margin. See
|
||||
// ScreenBufferTests::CursorUpDownOutsideMargins for a test of that
|
||||
// behavior.
|
||||
if (cursorPosition.y >= topMargin)
|
||||
// Vertical margins only apply if the original position is inside the
|
||||
// horizontal margins. Also, the cursor will only be clamped inside the
|
||||
// top margin if it was already below the top margin to start with, and
|
||||
// it will only be clamped inside the bottom margin if it was already
|
||||
// above the bottom margin to start with.
|
||||
if (cursorPosition.x >= leftMargin && cursorPosition.x <= rightMargin)
|
||||
{
|
||||
row = std::max(row, topMargin);
|
||||
if (cursorPosition.y >= topMargin)
|
||||
{
|
||||
row = std::max(row, topMargin);
|
||||
}
|
||||
if (cursorPosition.y <= bottomMargin)
|
||||
{
|
||||
row = std::min(row, bottomMargin);
|
||||
}
|
||||
}
|
||||
if (cursorPosition.y <= bottomMargin)
|
||||
// Similarly, horizontal margins only apply if the new row is inside the
|
||||
// vertical margins. And the cursor is only clamped inside the horizontal
|
||||
// margins if it was already inside to start with.
|
||||
if (row >= topMargin && row <= bottomMargin)
|
||||
{
|
||||
row = std::min(row, bottomMargin);
|
||||
if (cursorPosition.x >= leftMargin)
|
||||
{
|
||||
col = std::max(col, leftMargin);
|
||||
}
|
||||
if (cursorPosition.x <= rightMargin)
|
||||
{
|
||||
col = std::min(col, rightMargin);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -448,6 +503,7 @@ bool AdaptDispatch::CursorSaveState()
|
||||
// Although if origin mode is set, the cursor is relative to the margin origin.
|
||||
if (_modes.test(Mode::Origin))
|
||||
{
|
||||
cursorPosition.x -= _GetHorizontalMargins(textBuffer.GetSize().Width()).first;
|
||||
cursorPosition.y -= _GetVerticalMargins(viewport, false).first;
|
||||
}
|
||||
|
||||
@@ -520,14 +576,35 @@ void AdaptDispatch::_ScrollRectVertically(TextBuffer& textBuffer, const til::rec
|
||||
const auto absoluteDelta = std::min(std::abs(delta), scrollRect.height());
|
||||
if (absoluteDelta < scrollRect.height())
|
||||
{
|
||||
// For now we're assuming the scrollRect is always the full width of the
|
||||
// buffer, but this will likely need to be extended to support scrolling
|
||||
// of arbitrary widths at some point in the future.
|
||||
const auto top = delta > 0 ? scrollRect.top : scrollRect.top + absoluteDelta;
|
||||
const auto width = scrollRect.width();
|
||||
const auto height = scrollRect.height() - absoluteDelta;
|
||||
const auto actualDelta = delta > 0 ? absoluteDelta : -absoluteDelta;
|
||||
textBuffer.ScrollRows(top, height, actualDelta);
|
||||
textBuffer.TriggerRedraw(Viewport::FromExclusive(scrollRect));
|
||||
if (width == textBuffer.GetSize().Width())
|
||||
{
|
||||
// If the scrollRect is the full width of the buffer, we can scroll
|
||||
// more efficiently by rotating the row storage.
|
||||
textBuffer.ScrollRows(top, height, actualDelta);
|
||||
textBuffer.TriggerRedraw(Viewport::FromExclusive(scrollRect));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Otherwise we have to move the content up or down by copying the
|
||||
// requested buffer range one cell at a time.
|
||||
const auto srcOrigin = til::point{ scrollRect.left, top };
|
||||
const auto dstOrigin = til::point{ scrollRect.left, top + actualDelta };
|
||||
const auto srcView = Viewport::FromDimensions(srcOrigin, width, height);
|
||||
const auto dstView = Viewport::FromDimensions(dstOrigin, width, height);
|
||||
const auto walkDirection = Viewport::DetermineWalkDirection(srcView, dstView);
|
||||
auto srcPos = srcView.GetWalkOrigin(walkDirection);
|
||||
auto dstPos = dstView.GetWalkOrigin(walkDirection);
|
||||
do
|
||||
{
|
||||
const auto current = OutputCell(*textBuffer.GetCellDataAt(srcPos));
|
||||
textBuffer.WriteLine(OutputCellIterator({ ¤t, 1 }), dstPos);
|
||||
srcView.WalkInBounds(srcPos, walkDirection);
|
||||
} while (dstView.WalkInBounds(dstPos, walkDirection));
|
||||
}
|
||||
}
|
||||
|
||||
// Rows revealed by the scroll are filled with standard erase attributes.
|
||||
@@ -597,13 +674,21 @@ void AdaptDispatch::_ScrollRectHorizontally(TextBuffer& textBuffer, const til::r
|
||||
// - <none>
|
||||
void AdaptDispatch::_InsertDeleteCharacterHelper(const VTInt delta)
|
||||
{
|
||||
const auto viewport = _api.GetViewport();
|
||||
auto& textBuffer = _api.GetTextBuffer();
|
||||
const auto row = textBuffer.GetCursor().GetPosition().y;
|
||||
const auto startCol = textBuffer.GetCursor().GetPosition().x;
|
||||
const auto endCol = textBuffer.GetLineWidth(row);
|
||||
_ScrollRectHorizontally(textBuffer, { startCol, row, endCol, row + 1 }, delta);
|
||||
// The ICH and DCH controls are expected to reset the delayed wrap flag.
|
||||
textBuffer.GetCursor().ResetDelayEOLWrap();
|
||||
const auto col = textBuffer.GetCursor().GetPosition().x;
|
||||
const auto lineWidth = textBuffer.GetLineWidth(row);
|
||||
const auto [topMargin, bottomMargin] = _GetVerticalMargins(viewport, true);
|
||||
const auto [leftMargin, rightMargin] = (row >= topMargin && row <= bottomMargin) ?
|
||||
_GetHorizontalMargins(lineWidth) :
|
||||
std::make_pair(0, lineWidth - 1);
|
||||
if (col >= leftMargin && col <= rightMargin)
|
||||
{
|
||||
_ScrollRectHorizontally(textBuffer, { col, row, rightMargin + 1, row + 1 }, delta);
|
||||
// The ICH and DCH controls are expected to reset the delayed wrap flag.
|
||||
textBuffer.GetCursor().ResetDelayEOLWrap();
|
||||
}
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
@@ -972,15 +1057,17 @@ til::rect AdaptDispatch::_CalculateRectArea(const VTInt top, const VTInt left, c
|
||||
// We start by calculating the margin offsets and maximum dimensions.
|
||||
// If the origin mode isn't set, we use the viewport extent.
|
||||
const auto [topMargin, bottomMargin] = _GetVerticalMargins(viewport, false);
|
||||
const auto [leftMargin, rightMargin] = _GetHorizontalMargins(bufferSize.width);
|
||||
const auto yOffset = _modes.test(Mode::Origin) ? topMargin : 0;
|
||||
const auto yMaximum = _modes.test(Mode::Origin) ? bottomMargin + 1 : viewport.height();
|
||||
const auto xMaximum = bufferSize.width;
|
||||
const auto xOffset = _modes.test(Mode::Origin) ? leftMargin : 0;
|
||||
const auto xMaximum = _modes.test(Mode::Origin) ? rightMargin + 1 : bufferSize.width;
|
||||
|
||||
auto fillRect = til::inclusive_rect{};
|
||||
fillRect.left = left;
|
||||
fillRect.left = left + xOffset;
|
||||
fillRect.top = top + yOffset;
|
||||
// Right and bottom default to the maximum dimensions.
|
||||
fillRect.right = (right ? right : xMaximum);
|
||||
fillRect.right = (right ? right + xOffset : xMaximum);
|
||||
fillRect.bottom = (bottom ? bottom + yOffset : yMaximum);
|
||||
|
||||
// We also clamp everything to the maximum dimensions, and subtract 1
|
||||
@@ -1339,13 +1426,17 @@ bool AdaptDispatch::RequestChecksumRectangularArea(const VTInt id, const VTInt p
|
||||
// - True.
|
||||
bool AdaptDispatch::SetLineRendition(const LineRendition rendition)
|
||||
{
|
||||
auto& textBuffer = _api.GetTextBuffer();
|
||||
textBuffer.SetCurrentLineRendition(rendition);
|
||||
// There is some variation in how this was handled by the different DEC
|
||||
// terminals, but the STD 070 reference (on page D-13) makes it clear that
|
||||
// the delayed wrap (aka the Last Column Flag) was expected to be reset when
|
||||
// line rendition controls were executed.
|
||||
textBuffer.GetCursor().ResetDelayEOLWrap();
|
||||
// The line rendition can't be changed if left/right margins are allowed.
|
||||
if (!_modes.test(Mode::AllowDECSLRM))
|
||||
{
|
||||
auto& textBuffer = _api.GetTextBuffer();
|
||||
textBuffer.SetCurrentLineRendition(rendition);
|
||||
// There is some variation in how this was handled by the different DEC
|
||||
// terminals, but the STD 070 reference (on page D-13) makes it clear that
|
||||
// the delayed wrap (aka the Last Column Flag) was expected to be reset when
|
||||
// line rendition controls were executed.
|
||||
textBuffer.GetCursor().ResetDelayEOLWrap();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1526,11 +1617,11 @@ void AdaptDispatch::_CursorPositionReport(const bool extendedReport)
|
||||
cursorPosition.x++;
|
||||
cursorPosition.y++;
|
||||
|
||||
// If the origin mode is relative, line numbers start at top margin of the scrolling region.
|
||||
// If the origin mode is set, the cursor is relative to the margin origin.
|
||||
if (_modes.test(Mode::Origin))
|
||||
{
|
||||
const auto topMargin = _GetVerticalMargins(viewport, false).first;
|
||||
cursorPosition.y -= topMargin;
|
||||
cursorPosition.x -= _GetHorizontalMargins(textBuffer.GetSize().Width()).first;
|
||||
cursorPosition.y -= _GetVerticalMargins(viewport, false).first;
|
||||
}
|
||||
|
||||
// Now send it back into the input channel of the console.
|
||||
@@ -1590,7 +1681,8 @@ void AdaptDispatch::_ScrollMovement(const VTInt delta)
|
||||
auto& textBuffer = _api.GetTextBuffer();
|
||||
const auto bufferWidth = textBuffer.GetSize().Width();
|
||||
const auto [topMargin, bottomMargin] = _GetVerticalMargins(viewport, true);
|
||||
_ScrollRectVertically(textBuffer, { 0, topMargin, bufferWidth, bottomMargin + 1 }, delta);
|
||||
const auto [leftMargin, rightMargin] = _GetHorizontalMargins(bufferWidth);
|
||||
_ScrollRectVertically(textBuffer, { leftMargin, topMargin, rightMargin + 1, bottomMargin + 1 }, delta);
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
@@ -1634,10 +1726,11 @@ void AdaptDispatch::_SetColumnMode(const bool enable)
|
||||
const auto viewportWidth = (enable ? DispatchTypes::s_sDECCOLMSetColumns : DispatchTypes::s_sDECCOLMResetColumns);
|
||||
_api.ResizeWindow(viewportWidth, viewportHeight);
|
||||
_modes.set(Mode::Column, enable);
|
||||
_modes.reset(Mode::Origin);
|
||||
_modes.reset(Mode::Origin, Mode::AllowDECSLRM);
|
||||
CursorPosition(1, 1);
|
||||
EraseInDisplay(DispatchTypes::EraseType::All);
|
||||
_DoSetTopBottomScrollingMargins(0, 0);
|
||||
_DoSetLeftRightScrollingMargins(0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1696,6 +1789,15 @@ bool AdaptDispatch::_ModeParamsHelper(const DispatchTypes::ModeParams param, con
|
||||
case DispatchTypes::ModeParams::IRM_InsertReplaceMode:
|
||||
_modes.set(Mode::InsertReplace, enable);
|
||||
return true;
|
||||
case DispatchTypes::ModeParams::LNM_LineFeedNewLineMode:
|
||||
// VT apps expect that the system and input modes are the same, so if
|
||||
// they become out of sync, we just act as if LNM mode isn't supported.
|
||||
if (_api.GetSystemMode(ITerminalApi::Mode::LineFeed) == _terminalInput.GetInputMode(TerminalInput::Mode::LineFeed))
|
||||
{
|
||||
_api.SetSystemMode(ITerminalApi::Mode::LineFeed, enable);
|
||||
_terminalInput.SetInputMode(TerminalInput::Mode::LineFeed, enable);
|
||||
}
|
||||
return true;
|
||||
case DispatchTypes::ModeParams::DECCKM_CursorKeysMode:
|
||||
_terminalInput.SetInputMode(TerminalInput::Mode::CursorKey, enable);
|
||||
return !_PassThroughInputModes();
|
||||
@@ -1719,7 +1821,7 @@ bool AdaptDispatch::_ModeParamsHelper(const DispatchTypes::ModeParams param, con
|
||||
CursorPosition(1, 1);
|
||||
return true;
|
||||
case DispatchTypes::ModeParams::DECAWM_AutoWrapMode:
|
||||
_api.SetAutoWrapMode(enable);
|
||||
_api.SetSystemMode(ITerminalApi::Mode::AutoWrap, enable);
|
||||
// Resetting DECAWM should also reset the delayed wrap flag.
|
||||
if (!enable)
|
||||
{
|
||||
@@ -1744,6 +1846,17 @@ bool AdaptDispatch::_ModeParamsHelper(const DispatchTypes::ModeParams param, con
|
||||
case DispatchTypes::ModeParams::DECBKM_BackarrowKeyMode:
|
||||
_terminalInput.SetInputMode(TerminalInput::Mode::BackarrowKey, enable);
|
||||
return !_PassThroughInputModes();
|
||||
case DispatchTypes::ModeParams::DECLRMM_LeftRightMarginMode:
|
||||
_modes.set(Mode::AllowDECSLRM, enable);
|
||||
_DoSetLeftRightScrollingMargins(0, 0);
|
||||
if (enable)
|
||||
{
|
||||
// If we've allowed left/right margins, we can't have line renditions.
|
||||
const auto viewport = _api.GetViewport();
|
||||
auto& textBuffer = _api.GetTextBuffer();
|
||||
textBuffer.ResetLineRenditionRange(viewport.top, viewport.bottom);
|
||||
}
|
||||
return true;
|
||||
case DispatchTypes::ModeParams::VT200_MOUSE_MODE:
|
||||
_terminalInput.SetInputMode(TerminalInput::Mode::DefaultMouseTracking, enable);
|
||||
return !_PassThroughInputModes();
|
||||
@@ -1771,7 +1884,7 @@ bool AdaptDispatch::_ModeParamsHelper(const DispatchTypes::ModeParams param, con
|
||||
_SetAlternateScreenBufferMode(enable);
|
||||
return true;
|
||||
case DispatchTypes::ModeParams::XTERM_BracketedPasteMode:
|
||||
_api.SetBracketedPasteMode(enable);
|
||||
_api.SetSystemMode(ITerminalApi::Mode::BracketedPaste, enable);
|
||||
return !_api.IsConsolePty();
|
||||
case DispatchTypes::ModeParams::W32IM_Win32InputMode:
|
||||
_terminalInput.SetInputMode(TerminalInput::Mode::Win32, enable);
|
||||
@@ -1820,6 +1933,14 @@ bool AdaptDispatch::RequestMode(const DispatchTypes::ModeParams param)
|
||||
case DispatchTypes::ModeParams::IRM_InsertReplaceMode:
|
||||
enabled = _modes.test(Mode::InsertReplace);
|
||||
break;
|
||||
case DispatchTypes::ModeParams::LNM_LineFeedNewLineMode:
|
||||
// VT apps expect that the system and input modes are the same, so if
|
||||
// they become out of sync, we just act as if LNM mode isn't supported.
|
||||
if (_api.GetSystemMode(ITerminalApi::Mode::LineFeed) == _terminalInput.GetInputMode(TerminalInput::Mode::LineFeed))
|
||||
{
|
||||
enabled = _terminalInput.GetInputMode(TerminalInput::Mode::LineFeed);
|
||||
}
|
||||
break;
|
||||
case DispatchTypes::ModeParams::DECCKM_CursorKeysMode:
|
||||
enabled = _terminalInput.GetInputMode(TerminalInput::Mode::CursorKey);
|
||||
break;
|
||||
@@ -1840,7 +1961,7 @@ bool AdaptDispatch::RequestMode(const DispatchTypes::ModeParams param)
|
||||
enabled = _modes.test(Mode::Origin);
|
||||
break;
|
||||
case DispatchTypes::ModeParams::DECAWM_AutoWrapMode:
|
||||
enabled = _api.GetAutoWrapMode();
|
||||
enabled = _api.GetSystemMode(ITerminalApi::Mode::AutoWrap);
|
||||
break;
|
||||
case DispatchTypes::ModeParams::DECARM_AutoRepeatMode:
|
||||
enabled = _terminalInput.GetInputMode(TerminalInput::Mode::AutoRepeat);
|
||||
@@ -1864,6 +1985,9 @@ bool AdaptDispatch::RequestMode(const DispatchTypes::ModeParams param)
|
||||
case DispatchTypes::ModeParams::DECBKM_BackarrowKeyMode:
|
||||
enabled = _terminalInput.GetInputMode(TerminalInput::Mode::BackarrowKey);
|
||||
break;
|
||||
case DispatchTypes::ModeParams::DECLRMM_LeftRightMarginMode:
|
||||
enabled = _modes.test(Mode::AllowDECSLRM);
|
||||
break;
|
||||
case DispatchTypes::ModeParams::VT200_MOUSE_MODE:
|
||||
enabled = _terminalInput.GetInputMode(TerminalInput::Mode::DefaultMouseTracking);
|
||||
break;
|
||||
@@ -1889,7 +2013,7 @@ bool AdaptDispatch::RequestMode(const DispatchTypes::ModeParams param)
|
||||
enabled = _usingAltBuffer;
|
||||
break;
|
||||
case DispatchTypes::ModeParams::XTERM_BracketedPasteMode:
|
||||
enabled = _api.GetBracketedPasteMode();
|
||||
enabled = _api.GetSystemMode(ITerminalApi::Mode::BracketedPaste);
|
||||
break;
|
||||
case DispatchTypes::ModeParams::W32IM_Win32InputMode:
|
||||
enabled = _terminalInput.GetInputMode(TerminalInput::Mode::Win32);
|
||||
@@ -1934,17 +2058,18 @@ void AdaptDispatch::_InsertDeleteLineHelper(const int32_t delta)
|
||||
const auto bufferWidth = textBuffer.GetSize().Width();
|
||||
|
||||
auto& cursor = textBuffer.GetCursor();
|
||||
const auto col = cursor.GetPosition().x;
|
||||
const auto row = cursor.GetPosition().y;
|
||||
|
||||
const auto [topMargin, bottomMargin] = _GetVerticalMargins(viewport, true);
|
||||
if (row >= topMargin && row <= bottomMargin)
|
||||
const auto [leftMargin, rightMargin] = _GetHorizontalMargins(bufferWidth);
|
||||
if (row >= topMargin && row <= bottomMargin && col >= leftMargin && col <= rightMargin)
|
||||
{
|
||||
// We emulate inserting and deleting by scrolling the area between the cursor and the bottom margin.
|
||||
_ScrollRectVertically(textBuffer, { 0, row, bufferWidth, bottomMargin + 1 }, delta);
|
||||
_ScrollRectVertically(textBuffer, { leftMargin, row, rightMargin + 1, bottomMargin + 1 }, delta);
|
||||
|
||||
// The IL and DL controls are also expected to move the cursor to the left margin.
|
||||
// For now this is just column 0, since we don't yet support DECSLRM.
|
||||
cursor.SetXPosition(0);
|
||||
cursor.SetXPosition(leftMargin);
|
||||
_ApplyCursorMovementFlags(cursor);
|
||||
}
|
||||
}
|
||||
@@ -2008,10 +2133,12 @@ bool AdaptDispatch::SetAnsiMode(const bool ansiMode)
|
||||
// Arguments:
|
||||
// - topMargin - the line number for the top margin.
|
||||
// - bottomMargin - the line number for the bottom margin.
|
||||
// - homeCursor - move the cursor to the home position.
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void AdaptDispatch::_DoSetTopBottomScrollingMargins(const VTInt topMargin,
|
||||
const VTInt bottomMargin)
|
||||
const VTInt bottomMargin,
|
||||
const bool homeCursor)
|
||||
{
|
||||
// so notes time: (input -> state machine out -> adapter out -> conhost internal)
|
||||
// having only a top param is legal ([3;r -> 3,0 -> 3,h -> 3,h,true)
|
||||
@@ -2054,6 +2181,12 @@ void AdaptDispatch::_DoSetTopBottomScrollingMargins(const VTInt topMargin,
|
||||
}
|
||||
_scrollMargins.top = actualTop;
|
||||
_scrollMargins.bottom = actualBottom;
|
||||
// If requested, we may also need to move the cursor to the home
|
||||
// position, but only if the requested margins were valid.
|
||||
if (homeCursor)
|
||||
{
|
||||
CursorPosition(1, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2070,10 +2203,90 @@ void AdaptDispatch::_DoSetTopBottomScrollingMargins(const VTInt topMargin,
|
||||
bool AdaptDispatch::SetTopBottomScrollingMargins(const VTInt topMargin,
|
||||
const VTInt bottomMargin)
|
||||
{
|
||||
// When this is called, the cursor should also be moved to home.
|
||||
// Other functions that only need to set/reset the margins should call _DoSetTopBottomScrollingMargins
|
||||
_DoSetTopBottomScrollingMargins(topMargin, bottomMargin);
|
||||
CursorPosition(1, 1);
|
||||
_DoSetTopBottomScrollingMargins(topMargin, bottomMargin, true);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - DECSLRM - Set Scrolling Region
|
||||
// This control function sets the left and right margins for the current page.
|
||||
// You cannot perform scrolling outside the margins.
|
||||
// Default: Margins are at the page limits.
|
||||
// Arguments:
|
||||
// - leftMargin - the column number for the left margin.
|
||||
// - rightMargin - the column number for the right margin.
|
||||
// - homeCursor - move the cursor to the home position.
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void AdaptDispatch::_DoSetLeftRightScrollingMargins(const VTInt leftMargin,
|
||||
const VTInt rightMargin,
|
||||
const bool homeCursor)
|
||||
{
|
||||
til::CoordType actualLeft = leftMargin;
|
||||
til::CoordType actualRight = rightMargin;
|
||||
|
||||
const auto& textBuffer = _api.GetTextBuffer();
|
||||
const auto bufferWidth = textBuffer.GetSize().Width();
|
||||
// The default left margin is column 1
|
||||
if (actualLeft == 0)
|
||||
{
|
||||
actualLeft = 1;
|
||||
}
|
||||
// The default right margin is the buffer width
|
||||
if (actualRight == 0)
|
||||
{
|
||||
actualRight = bufferWidth;
|
||||
}
|
||||
// The left margin must be less than the right margin, and the
|
||||
// right margin must be less than or equal to the buffer width
|
||||
if (actualLeft < actualRight && actualRight <= bufferWidth)
|
||||
{
|
||||
if (actualLeft == 1 && actualRight == bufferWidth)
|
||||
{
|
||||
// Client requests setting margins to the entire screen
|
||||
// - clear them instead of setting them.
|
||||
actualLeft = 0;
|
||||
actualRight = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
// In VT, the origin is 1,1. For our array, it's 0,0. So subtract 1.
|
||||
actualLeft -= 1;
|
||||
actualRight -= 1;
|
||||
}
|
||||
_scrollMargins.left = actualLeft;
|
||||
_scrollMargins.right = actualRight;
|
||||
// If requested, we may also need to move the cursor to the home
|
||||
// position, but only if the requested margins were valid.
|
||||
if (homeCursor)
|
||||
{
|
||||
CursorPosition(1, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - DECSLRM - Set Scrolling Region
|
||||
// This control function sets the left and right margins for the current page.
|
||||
// You cannot perform scrolling outside the margins.
|
||||
// Default: Margins are at the page limits.
|
||||
// Arguments:
|
||||
// - leftMargin - the column number for the left margin.
|
||||
// - rightMargin - the column number for the right margin.
|
||||
// Return Value:
|
||||
// - True.
|
||||
bool AdaptDispatch::SetLeftRightScrollingMargins(const VTInt leftMargin,
|
||||
const VTInt rightMargin)
|
||||
{
|
||||
if (_modes.test(Mode::AllowDECSLRM))
|
||||
{
|
||||
_DoSetLeftRightScrollingMargins(leftMargin, rightMargin, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
// When DECSLRM isn't allowed, `CSI s` is interpreted as ANSISYSSC.
|
||||
CursorSaveState();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -2113,9 +2326,10 @@ bool AdaptDispatch::CarriageReturn()
|
||||
void AdaptDispatch::_DoLineFeed(TextBuffer& textBuffer, const bool withReturn, const bool wrapForced)
|
||||
{
|
||||
const auto viewport = _api.GetViewport();
|
||||
const auto [topMargin, bottomMargin] = _GetVerticalMargins(viewport, true);
|
||||
const auto bufferWidth = textBuffer.GetSize().Width();
|
||||
const auto bufferHeight = textBuffer.GetSize().Height();
|
||||
const auto [topMargin, bottomMargin] = _GetVerticalMargins(viewport, true);
|
||||
const auto [leftMargin, rightMargin] = _GetHorizontalMargins(bufferWidth);
|
||||
|
||||
auto& cursor = textBuffer.GetCursor();
|
||||
const auto currentPosition = cursor.GetPosition();
|
||||
@@ -2125,18 +2339,30 @@ void AdaptDispatch::_DoLineFeed(TextBuffer& textBuffer, const bool withReturn, c
|
||||
// When explicitly moving down a row, clear the wrap status.
|
||||
textBuffer.GetRowByOffset(currentPosition.y).SetWrapForced(wrapForced);
|
||||
|
||||
if (currentPosition.y != bottomMargin)
|
||||
// If a carriage return was requested, we move to the leftmost column or
|
||||
// the left margin, depending on whether we started within the margins.
|
||||
if (withReturn)
|
||||
{
|
||||
// If we're not at the bottom margin then there's no scrolling,
|
||||
// so we make sure we don't move past the bottom of the viewport.
|
||||
const auto clampToMargin = currentPosition.y >= topMargin &&
|
||||
currentPosition.y <= bottomMargin &&
|
||||
currentPosition.x >= leftMargin;
|
||||
newPosition.x = clampToMargin ? leftMargin : 0;
|
||||
}
|
||||
|
||||
if (currentPosition.y != bottomMargin || newPosition.x < leftMargin || newPosition.x > rightMargin)
|
||||
{
|
||||
// If we're not at the bottom margin, or outside the horizontal margins,
|
||||
// then there's no scrolling, so we make sure we don't move past the
|
||||
// bottom of the viewport.
|
||||
newPosition.y = std::min(currentPosition.y + 1, viewport.bottom - 1);
|
||||
newPosition = textBuffer.ClampPositionWithinLine(newPosition);
|
||||
}
|
||||
else if (topMargin > viewport.top)
|
||||
else if (topMargin > viewport.top || leftMargin > 0 || rightMargin < bufferWidth - 1)
|
||||
{
|
||||
// If the top margin isn't at the top of the viewport, then we're
|
||||
// just scrolling the margin area and the cursor stays where it is.
|
||||
_ScrollRectVertically(textBuffer, { 0, topMargin, bufferWidth, bottomMargin + 1 }, -1);
|
||||
// If the top margin isn't at the top of the viewport, or the
|
||||
// horizontal margins are set, then we're just scrolling the margin
|
||||
// area and the cursor stays where it is.
|
||||
_ScrollRectVertically(textBuffer, { leftMargin, topMargin, rightMargin + 1, bottomMargin + 1 }, -1);
|
||||
}
|
||||
else if (viewport.bottom < bufferHeight)
|
||||
{
|
||||
@@ -2183,9 +2409,6 @@ void AdaptDispatch::_DoLineFeed(TextBuffer& textBuffer, const bool withReturn, c
|
||||
}
|
||||
}
|
||||
|
||||
// If a carriage return was requested, we also move to the leftmost column.
|
||||
newPosition.x = withReturn ? 0 : newPosition.x;
|
||||
|
||||
cursor.SetPosition(newPosition);
|
||||
_ApplyCursorMovementFlags(cursor);
|
||||
}
|
||||
@@ -2203,7 +2426,7 @@ bool AdaptDispatch::LineFeed(const DispatchTypes::LineFeedType lineFeedType)
|
||||
switch (lineFeedType)
|
||||
{
|
||||
case DispatchTypes::LineFeedType::DependsOnMode:
|
||||
_DoLineFeed(textBuffer, _api.GetLineFeedMode(), false);
|
||||
_DoLineFeed(textBuffer, _api.GetSystemMode(ITerminalApi::Mode::LineFeed), false);
|
||||
return true;
|
||||
case DispatchTypes::LineFeedType::WithoutReturn:
|
||||
_DoLineFeed(textBuffer, false, false);
|
||||
@@ -2229,14 +2452,15 @@ bool AdaptDispatch::ReverseLineFeed()
|
||||
auto& textBuffer = _api.GetTextBuffer();
|
||||
auto& cursor = textBuffer.GetCursor();
|
||||
const auto cursorPosition = cursor.GetPosition();
|
||||
const auto bufferWidth = textBuffer.GetSize().Width();
|
||||
const auto [leftMargin, rightMargin] = _GetHorizontalMargins(bufferWidth);
|
||||
const auto [topMargin, bottomMargin] = _GetVerticalMargins(viewport, true);
|
||||
|
||||
// If the cursor is at the top of the margin area, we shift the buffer
|
||||
// contents down, to emulate inserting a line at that point.
|
||||
if (cursorPosition.y == topMargin)
|
||||
if (cursorPosition.y == topMargin && cursorPosition.x >= leftMargin && cursorPosition.x <= rightMargin)
|
||||
{
|
||||
const auto bufferWidth = textBuffer.GetSize().Width();
|
||||
_ScrollRectVertically(textBuffer, { 0, topMargin, bufferWidth, bottomMargin + 1 }, 1);
|
||||
_ScrollRectVertically(textBuffer, { leftMargin, topMargin, rightMargin + 1, bottomMargin + 1 }, 1);
|
||||
}
|
||||
else if (cursorPosition.y > viewport.top)
|
||||
{
|
||||
@@ -2290,12 +2514,19 @@ bool AdaptDispatch::ForwardTab(const VTInt numTabs)
|
||||
{
|
||||
auto& textBuffer = _api.GetTextBuffer();
|
||||
auto& cursor = textBuffer.GetCursor();
|
||||
const auto width = textBuffer.GetLineWidth(cursor.GetPosition().y);
|
||||
auto column = cursor.GetPosition().x;
|
||||
const auto row = cursor.GetPosition().y;
|
||||
const auto width = textBuffer.GetLineWidth(row);
|
||||
auto tabsPerformed = 0;
|
||||
|
||||
const auto viewport = _api.GetViewport();
|
||||
const auto [topMargin, bottomMargin] = _GetVerticalMargins(viewport, true);
|
||||
const auto [leftMargin, rightMargin] = _GetHorizontalMargins(width);
|
||||
const auto clampToMargin = row >= topMargin && row <= bottomMargin && column <= rightMargin;
|
||||
const auto maxColumn = clampToMargin ? rightMargin : width - 1;
|
||||
|
||||
_InitTabStopsForWidth(width);
|
||||
while (column + 1 < width && tabsPerformed < numTabs)
|
||||
while (column < maxColumn && tabsPerformed < numTabs)
|
||||
{
|
||||
column++;
|
||||
if (til::at(_tabStopColumns, column))
|
||||
@@ -2332,12 +2563,19 @@ bool AdaptDispatch::BackwardsTab(const VTInt numTabs)
|
||||
{
|
||||
auto& textBuffer = _api.GetTextBuffer();
|
||||
auto& cursor = textBuffer.GetCursor();
|
||||
const auto width = textBuffer.GetLineWidth(cursor.GetPosition().y);
|
||||
auto column = cursor.GetPosition().x;
|
||||
const auto row = cursor.GetPosition().y;
|
||||
const auto width = textBuffer.GetLineWidth(row);
|
||||
auto tabsPerformed = 0;
|
||||
|
||||
const auto viewport = _api.GetViewport();
|
||||
const auto [topMargin, bottomMargin] = _GetVerticalMargins(viewport, true);
|
||||
const auto [leftMargin, rightMargin] = _GetHorizontalMargins(width);
|
||||
const auto clampToMargin = row >= topMargin && row <= bottomMargin && column >= leftMargin;
|
||||
const auto minColumn = clampToMargin ? leftMargin : 0;
|
||||
|
||||
_InitTabStopsForWidth(width);
|
||||
while (column > 0 && tabsPerformed < numTabs)
|
||||
while (column > minColumn && tabsPerformed < numTabs)
|
||||
{
|
||||
column--;
|
||||
if (til::at(_tabStopColumns, column))
|
||||
@@ -2589,13 +2827,18 @@ bool AdaptDispatch::AcceptC1Controls(const bool enabled)
|
||||
bool AdaptDispatch::SoftReset()
|
||||
{
|
||||
_api.GetTextBuffer().GetCursor().SetIsVisible(true); // Cursor enabled.
|
||||
_modes.reset(Mode::InsertReplace, Mode::Origin); // Replace mode; Absolute cursor addressing.
|
||||
_api.SetAutoWrapMode(true); // Wrap at end of line.
|
||||
|
||||
// Replace mode; Absolute cursor addressing; Disallow left/right margins.
|
||||
_modes.reset(Mode::InsertReplace, Mode::Origin, Mode::AllowDECSLRM);
|
||||
|
||||
_api.SetSystemMode(ITerminalApi::Mode::AutoWrap, true); // Wrap at end of line.
|
||||
_terminalInput.SetInputMode(TerminalInput::Mode::CursorKey, false); // Normal characters.
|
||||
_terminalInput.SetInputMode(TerminalInput::Mode::Keypad, false); // Numeric characters.
|
||||
|
||||
// Top margin = 1; bottom margin = page length.
|
||||
_DoSetTopBottomScrollingMargins(0, 0);
|
||||
// Left margin = 1; right margin = page width.
|
||||
_DoSetLeftRightScrollingMargins(0, 0);
|
||||
|
||||
_termOutput = {}; // Reset all character set designations.
|
||||
if (_initialCodePage.has_value())
|
||||
@@ -2663,11 +2906,20 @@ bool AdaptDispatch::HardReset()
|
||||
// Cursor to 1,1 - the Soft Reset guarantees this is absolute
|
||||
CursorPosition(1, 1);
|
||||
|
||||
// We only reset the system line feed mode if the input mode is set. If it
|
||||
// isn't set, that either means they're both reset, and there's nothing for
|
||||
// us to do, or they're out of sync, which implies the system mode was set
|
||||
// via the console API, so it's not our responsibility.
|
||||
if (_terminalInput.GetInputMode(TerminalInput::Mode::LineFeed))
|
||||
{
|
||||
_api.SetSystemMode(ITerminalApi::Mode::LineFeed, false);
|
||||
}
|
||||
|
||||
// Reset input modes to their initial state
|
||||
_terminalInput.ResetInputModes();
|
||||
|
||||
// Reset bracketed paste mode
|
||||
_api.SetBracketedPasteMode(false);
|
||||
_api.SetSystemMode(ITerminalApi::Mode::BracketedPaste, false);
|
||||
|
||||
// Restore cursor blinking mode.
|
||||
_api.GetTextBuffer().GetCursor().SetBlinkingAllowed(true);
|
||||
@@ -2718,10 +2970,11 @@ bool AdaptDispatch::ScreenAlignmentPattern()
|
||||
auto attr = textBuffer.GetCurrentAttributes();
|
||||
attr.SetStandardErase();
|
||||
_api.SetTextAttributes(attr);
|
||||
// Reset the origin mode to absolute.
|
||||
_modes.reset(Mode::Origin);
|
||||
// Reset the origin mode to absolute, and disallow left/right margins.
|
||||
_modes.reset(Mode::Origin, Mode::AllowDECSLRM);
|
||||
// Clear the scrolling margins.
|
||||
_DoSetTopBottomScrollingMargins(0, 0);
|
||||
_DoSetLeftRightScrollingMargins(0, 0);
|
||||
// Set the cursor position to home.
|
||||
CursorPosition(1, 1);
|
||||
|
||||
@@ -3623,6 +3876,9 @@ ITermDispatch::StringHandler AdaptDispatch::RequestSetting()
|
||||
case VTID("r"):
|
||||
_ReportDECSTBMSetting();
|
||||
break;
|
||||
case VTID("s"):
|
||||
_ReportDECSLRMSetting();
|
||||
break;
|
||||
case VTID("\"q"):
|
||||
_ReportDECSCASetting();
|
||||
break;
|
||||
@@ -3751,6 +4007,30 @@ void AdaptDispatch::_ReportDECSTBMSetting()
|
||||
_api.ReturnResponse({ response.data(), response.size() });
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Reports the DECSLRM margin range in response to a DECRQSS query.
|
||||
// Arguments:
|
||||
// - None
|
||||
// Return Value:
|
||||
// - None
|
||||
void AdaptDispatch::_ReportDECSLRMSetting()
|
||||
{
|
||||
using namespace std::string_view_literals;
|
||||
|
||||
// A valid response always starts with DCS 1 $ r.
|
||||
fmt::basic_memory_buffer<wchar_t, 64> response;
|
||||
response.append(L"\033P1$r"sv);
|
||||
|
||||
const auto bufferWidth = _api.GetTextBuffer().GetSize().Width();
|
||||
const auto [marginLeft, marginRight] = _GetHorizontalMargins(bufferWidth);
|
||||
// VT origin is at 1,1 so we need to add 1 to these margins.
|
||||
fmt::format_to(std::back_inserter(response), FMT_COMPILE(L"{};{}"), marginLeft + 1, marginRight + 1);
|
||||
|
||||
// The 's' indicates this is an DECSLRM response, and ST ends the sequence.
|
||||
response.append(L"s\033\\"sv);
|
||||
_api.ReturnResponse({ response.data(), response.size() });
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Reports the DECSCA protected attribute in response to a DECRQSS query.
|
||||
// Arguments:
|
||||
@@ -3899,9 +4179,10 @@ void AdaptDispatch::_ReportCursorInformation()
|
||||
cursorPosition.x++;
|
||||
cursorPosition.y++;
|
||||
|
||||
// If the origin mode is relative, line numbers start at top of the scrolling region.
|
||||
// If the origin mode is set, the cursor is relative to the margin origin.
|
||||
if (_modes.test(Mode::Origin))
|
||||
{
|
||||
cursorPosition.x -= _GetHorizontalMargins(textBuffer.GetSize().Width()).first;
|
||||
cursorPosition.y -= _GetVerticalMargins(viewport, false).first;
|
||||
}
|
||||
|
||||
|
||||
@@ -90,6 +90,8 @@ namespace Microsoft::Console::VirtualTerminal
|
||||
bool SetAnsiMode(const bool ansiMode) override; // DECANM
|
||||
bool SetTopBottomScrollingMargins(const VTInt topMargin,
|
||||
const VTInt bottomMargin) override; // DECSTBM
|
||||
bool SetLeftRightScrollingMargins(const VTInt leftMargin,
|
||||
const VTInt rightMargin) override; // DECSLRM
|
||||
bool WarningBell() override; // BEL
|
||||
bool CarriageReturn() override; // CR
|
||||
bool LineFeed(const DispatchTypes::LineFeedType lineFeedType) override; // IND, NEL, LF, FF, VT
|
||||
@@ -163,6 +165,7 @@ namespace Microsoft::Console::VirtualTerminal
|
||||
Origin,
|
||||
Column,
|
||||
AllowDECCOLM,
|
||||
AllowDECSLRM,
|
||||
RectangularChangeExtent
|
||||
};
|
||||
enum class ScrollDirection
|
||||
@@ -201,6 +204,7 @@ namespace Microsoft::Console::VirtualTerminal
|
||||
|
||||
void _WriteToBuffer(const std::wstring_view string);
|
||||
std::pair<int, int> _GetVerticalMargins(const til::rect& viewport, const bool absolute) noexcept;
|
||||
std::pair<int, int> _GetHorizontalMargins(const til::CoordType bufferWidth) noexcept;
|
||||
bool _CursorMovePosition(const Offset rowOffset, const Offset colOffset, const bool clampInMargins);
|
||||
void _ApplyCursorMovementFlags(Cursor& cursor) noexcept;
|
||||
void _FillRect(TextBuffer& textBuffer, const til::rect& fillRect, const wchar_t fillChar, const TextAttribute fillAttrs);
|
||||
@@ -217,7 +221,12 @@ namespace Microsoft::Console::VirtualTerminal
|
||||
void _ScrollMovement(const VTInt delta);
|
||||
|
||||
void _DoSetTopBottomScrollingMargins(const VTInt topMargin,
|
||||
const VTInt bottomMargin);
|
||||
const VTInt bottomMargin,
|
||||
const bool homeCursor = false);
|
||||
void _DoSetLeftRightScrollingMargins(const VTInt leftMargin,
|
||||
const VTInt rightMargin,
|
||||
const bool homeCursor = false);
|
||||
|
||||
void _DoLineFeed(TextBuffer& textBuffer, const bool withReturn, const bool wrapForced);
|
||||
|
||||
void _OperatingStatus() const;
|
||||
@@ -239,6 +248,7 @@ namespace Microsoft::Console::VirtualTerminal
|
||||
|
||||
void _ReportSGRSetting() const;
|
||||
void _ReportDECSTBMSetting();
|
||||
void _ReportDECSLRMSetting();
|
||||
void _ReportDECSCASetting() const;
|
||||
void _ReportDECSACESetting() const;
|
||||
void _ReportDECACSetting(const VTInt itemNumber) const;
|
||||
|
||||
@@ -47,6 +47,7 @@ public:
|
||||
bool SetKeypadMode(const bool /*applicationMode*/) override { return false; } // DECKPAM, DECKPNM
|
||||
bool SetAnsiMode(const bool /*ansiMode*/) override { return false; } // DECANM
|
||||
bool SetTopBottomScrollingMargins(const VTInt /*topMargin*/, const VTInt /*bottomMargin*/) override { return false; } // DECSTBM
|
||||
bool SetLeftRightScrollingMargins(const VTInt /*leftMargin*/, const VTInt /*rightMargin*/) override { return false; } // DECSLRM
|
||||
bool WarningBell() override { return false; } // BEL
|
||||
bool CarriageReturn() override { return false; } // CR
|
||||
bool LineFeed(const DispatchTypes::LineFeedType /*lineFeedType*/) override { return false; } // IND, NEL, LF, FF, VT
|
||||
|
||||
@@ -96,17 +96,6 @@ public:
|
||||
Log::Comment(L"SetViewportPosition MOCK called...");
|
||||
}
|
||||
|
||||
void SetAutoWrapMode(const bool /*wrapAtEOL*/) override
|
||||
{
|
||||
Log::Comment(L"SetAutoWrapMode MOCK called...");
|
||||
}
|
||||
|
||||
bool GetAutoWrapMode() const override
|
||||
{
|
||||
Log::Comment(L"GetAutoWrapMode MOCK called...");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IsVtInputEnabled() const override
|
||||
{
|
||||
return false;
|
||||
@@ -121,17 +110,23 @@ public:
|
||||
_textBuffer->SetCurrentAttributes(attrs);
|
||||
}
|
||||
|
||||
void SetSystemMode(const Mode mode, const bool enabled)
|
||||
{
|
||||
Log::Comment(L"SetSystemMode MOCK called...");
|
||||
_systemMode.set(mode, enabled);
|
||||
}
|
||||
|
||||
bool GetSystemMode(const Mode mode) const
|
||||
{
|
||||
Log::Comment(L"GetSystemMode MOCK called...");
|
||||
return _systemMode.test(mode);
|
||||
}
|
||||
|
||||
void WarningBell() override
|
||||
{
|
||||
Log::Comment(L"WarningBell MOCK called...");
|
||||
}
|
||||
|
||||
bool GetLineFeedMode() const override
|
||||
{
|
||||
Log::Comment(L"GetLineFeedMode MOCK called...");
|
||||
return _getLineFeedModeResult;
|
||||
}
|
||||
|
||||
void SetWindowTitle(const std::wstring_view title)
|
||||
{
|
||||
Log::Comment(L"SetWindowTitle MOCK called...");
|
||||
@@ -184,17 +179,6 @@ public:
|
||||
return _expectedOutputCP;
|
||||
}
|
||||
|
||||
void SetBracketedPasteMode(const bool /*enabled*/) override
|
||||
{
|
||||
Log::Comment(L"SetBracketedPasteMode MOCK called...");
|
||||
}
|
||||
|
||||
bool GetBracketedPasteMode() const override
|
||||
{
|
||||
Log::Comment(L"GetBracketedPasteMode MOCK called...");
|
||||
return false;
|
||||
}
|
||||
|
||||
void CopyToClipboard(const std::wstring_view /*content*/)
|
||||
{
|
||||
Log::Comment(L"CopyToClipboard MOCK called...");
|
||||
@@ -386,7 +370,7 @@ public:
|
||||
bool _setTextAttributesResult = false;
|
||||
bool _returnResponseResult = false;
|
||||
|
||||
bool _getLineFeedModeResult = false;
|
||||
til::enumset<Mode> _systemMode{ Mode::AutoWrap };
|
||||
|
||||
bool _setWindowTitleResult = false;
|
||||
std::wstring_view _expectedWindowTitle{};
|
||||
@@ -1629,6 +1613,22 @@ public:
|
||||
requestSetting(L"r");
|
||||
_testGetSet->ValidateInputEvent(L"\033P1$r1;25r\033\\");
|
||||
|
||||
Log::Comment(L"Requesting DECSLRM margins (5 to 10).");
|
||||
_testGetSet->PrepData();
|
||||
// We need to enable DECLRMM for horizontal margins to work.
|
||||
_pDispatch->SetMode(DispatchTypes::DECLRMM_LeftRightMarginMode);
|
||||
_pDispatch->SetLeftRightScrollingMargins(5, 10);
|
||||
requestSetting(L"s");
|
||||
_testGetSet->ValidateInputEvent(L"\033P1$r5;10s\033\\");
|
||||
|
||||
Log::Comment(L"Requesting DECSLRM margins (full width).");
|
||||
_testGetSet->PrepData();
|
||||
_pDispatch->SetLeftRightScrollingMargins(0, 0);
|
||||
requestSetting(L"s");
|
||||
_testGetSet->ValidateInputEvent(L"\033P1$r1;100s\033\\");
|
||||
// Reset DECLRMM once we're done with horizontal margin testing.
|
||||
_pDispatch->ResetMode(DispatchTypes::DECLRMM_LeftRightMarginMode);
|
||||
|
||||
Log::Comment(L"Requesting SGR attributes (default).");
|
||||
_testGetSet->PrepData();
|
||||
TextAttribute attribute = {};
|
||||
@@ -1755,7 +1755,39 @@ public:
|
||||
_testGetSet->ValidateInputEvent(L"\033P0$r\033\\");
|
||||
}
|
||||
|
||||
TEST_METHOD(RequestModeTests)
|
||||
TEST_METHOD(RequestStandardModeTests)
|
||||
{
|
||||
// The mode numbers below correspond to the ANSIStandardMode values
|
||||
// in the ModeParams enum in DispatchTypes.hpp.
|
||||
|
||||
BEGIN_TEST_METHOD_PROPERTIES()
|
||||
TEST_METHOD_PROPERTY(L"Data:modeNumber", L"{4, 20}")
|
||||
END_TEST_METHOD_PROPERTIES()
|
||||
|
||||
VTInt modeNumber;
|
||||
VERIFY_SUCCEEDED_RETURN(TestData::TryGetValue(L"modeNumber", modeNumber));
|
||||
const auto mode = DispatchTypes::ANSIStandardMode(modeNumber);
|
||||
|
||||
// DISABLE_
|
||||
Log::Comment(NoThrowString().Format(L"Setting standard mode %d", modeNumber));
|
||||
_testGetSet->PrepData();
|
||||
VERIFY_IS_TRUE(_pDispatch->SetMode(mode));
|
||||
VERIFY_IS_TRUE(_pDispatch->RequestMode(mode));
|
||||
|
||||
wchar_t expectedResponse[20];
|
||||
swprintf_s(expectedResponse, ARRAYSIZE(expectedResponse), L"\x1b[%d;1$y", modeNumber);
|
||||
_testGetSet->ValidateInputEvent(expectedResponse);
|
||||
|
||||
Log::Comment(NoThrowString().Format(L"Resetting standard mode %d", modeNumber));
|
||||
_testGetSet->PrepData();
|
||||
VERIFY_IS_TRUE(_pDispatch->ResetMode(mode));
|
||||
VERIFY_IS_TRUE(_pDispatch->RequestMode(mode));
|
||||
|
||||
swprintf_s(expectedResponse, ARRAYSIZE(expectedResponse), L"\x1b[%d;2$y", modeNumber);
|
||||
_testGetSet->ValidateInputEvent(expectedResponse);
|
||||
}
|
||||
|
||||
TEST_METHOD(RequestPrivateModeTests)
|
||||
{
|
||||
// The mode numbers below correspond to the DECPrivateMode values
|
||||
// in the ModeParams enum in DispatchTypes.hpp. We don't include
|
||||
@@ -1763,7 +1795,7 @@ public:
|
||||
// and DECRQM would not then be applicable.
|
||||
|
||||
BEGIN_TEST_METHOD_PROPERTIES()
|
||||
TEST_METHOD_PROPERTY(L"Data:modeNumber", L"{1, 3, 5, 6, 8, 12, 25, 40, 66, 67, 1000, 1002, 1003, 1004, 1005, 1006, 1007, 1049, 9001}")
|
||||
TEST_METHOD_PROPERTY(L"Data:modeNumber", L"{1, 3, 5, 6, 7, 8, 12, 25, 40, 66, 67, 69, 1000, 1002, 1003, 1004, 1005, 1006, 1007, 1049, 2004, 9001}")
|
||||
END_TEST_METHOD_PROPERTIES()
|
||||
|
||||
VTInt modeNumber;
|
||||
@@ -2262,13 +2294,13 @@ public:
|
||||
VERIFY_ARE_EQUAL(til::point(0, 1), cursor.GetPosition());
|
||||
|
||||
Log::Comment(L"Test 3: Line feed depends on mode, and mode reset.");
|
||||
_testGetSet->_getLineFeedModeResult = false;
|
||||
_testGetSet->_systemMode.reset(ITerminalApi::Mode::LineFeed);
|
||||
cursor.SetPosition({ 10, 0 });
|
||||
VERIFY_IS_TRUE(_pDispatch->LineFeed(DispatchTypes::LineFeedType::DependsOnMode));
|
||||
VERIFY_ARE_EQUAL(til::point(10, 1), cursor.GetPosition());
|
||||
|
||||
Log::Comment(L"Test 4: Line feed depends on mode, and mode set.");
|
||||
_testGetSet->_getLineFeedModeResult = true;
|
||||
_testGetSet->_systemMode.set(ITerminalApi::Mode::LineFeed);
|
||||
cursor.SetPosition({ 10, 0 });
|
||||
VERIFY_IS_TRUE(_pDispatch->LineFeed(DispatchTypes::LineFeedType::DependsOnMode));
|
||||
VERIFY_ARE_EQUAL(til::point(0, 1), cursor.GetPosition());
|
||||
|
||||
@@ -600,6 +600,15 @@ bool TerminalInput::HandleKey(const IInputEvent* const pInEvent)
|
||||
return true;
|
||||
}
|
||||
|
||||
// When the Line Feed mode is set, a VK_RETURN key should send both CR and LF.
|
||||
// When reset, we fall through to the default behavior, which is to send just
|
||||
// CR, or when the Ctrl modifier is pressed, just LF.
|
||||
if (keyEvent.GetVirtualKeyCode() == VK_RETURN && _inputMode.test(Mode::LineFeed))
|
||||
{
|
||||
_SendInputSequence(L"\r\n");
|
||||
return true;
|
||||
}
|
||||
|
||||
// Many keyboard layouts have an AltGr key, which makes widely used characters accessible.
|
||||
// For instance on a German keyboard layout "[" is written by pressing AltGr+8.
|
||||
// Furthermore Ctrl+Alt is traditionally treated as an alternative way to AltGr by Windows.
|
||||
|
||||
@@ -38,6 +38,7 @@ namespace Microsoft::Console::VirtualTerminal
|
||||
|
||||
enum class Mode : size_t
|
||||
{
|
||||
LineFeed,
|
||||
Ansi,
|
||||
AutoRepeat,
|
||||
Keypad,
|
||||
|
||||
@@ -491,10 +491,15 @@ bool OutputStateMachineEngine::ActionCsiDispatch(const VTID id, const VTParamete
|
||||
success = _dispatch->CursorPosition(parameters.at(0), parameters.at(1));
|
||||
TermTelemetry::Instance().Log(TermTelemetry::Codes::CUP);
|
||||
break;
|
||||
case CsiActionCodes::DECSTBM_SetScrollingRegion:
|
||||
case CsiActionCodes::DECSTBM_SetTopBottomMargins:
|
||||
success = _dispatch->SetTopBottomScrollingMargins(parameters.at(0).value_or(0), parameters.at(1).value_or(0));
|
||||
TermTelemetry::Instance().Log(TermTelemetry::Codes::DECSTBM);
|
||||
break;
|
||||
case CsiActionCodes::DECSLRM_SetLeftRightMargins:
|
||||
// Note that this can also be ANSISYSSC, depending on the state of DECLRMM.
|
||||
success = _dispatch->SetLeftRightScrollingMargins(parameters.at(0).value_or(0), parameters.at(1).value_or(0));
|
||||
TermTelemetry::Instance().Log(TermTelemetry::Codes::DECSTBM);
|
||||
break;
|
||||
case CsiActionCodes::ICH_InsertCharacter:
|
||||
success = _dispatch->InsertCharacter(parameters.at(0));
|
||||
TermTelemetry::Instance().Log(TermTelemetry::Codes::ICH);
|
||||
@@ -588,12 +593,8 @@ bool OutputStateMachineEngine::ActionCsiDispatch(const VTID id, const VTParamete
|
||||
success = _dispatch->ScrollDown(parameters.at(0));
|
||||
TermTelemetry::Instance().Log(TermTelemetry::Codes::SD);
|
||||
break;
|
||||
case CsiActionCodes::ANSISYSSC_CursorSave:
|
||||
success = parameters.empty() && _dispatch->CursorSaveState();
|
||||
TermTelemetry::Instance().Log(TermTelemetry::Codes::ANSISYSSC);
|
||||
break;
|
||||
case CsiActionCodes::ANSISYSRC_CursorRestore:
|
||||
success = parameters.empty() && _dispatch->CursorRestoreState();
|
||||
success = _dispatch->CursorRestoreState();
|
||||
TermTelemetry::Instance().Log(TermTelemetry::Codes::ANSISYSRC);
|
||||
break;
|
||||
case CsiActionCodes::IL_InsertLine:
|
||||
|
||||
@@ -137,8 +137,8 @@ namespace Microsoft::Console::VirtualTerminal
|
||||
SGR_SetGraphicsRendition = VTID("m"),
|
||||
DSR_DeviceStatusReport = VTID("n"),
|
||||
DSR_PrivateDeviceStatusReport = VTID("?n"),
|
||||
DECSTBM_SetScrollingRegion = VTID("r"),
|
||||
ANSISYSSC_CursorSave = VTID("s"), // NOTE: Overlaps with DECLRMM/DECSLRM. Fix when/if implemented.
|
||||
DECSTBM_SetTopBottomMargins = VTID("r"),
|
||||
DECSLRM_SetLeftRightMargins = VTID("s"),
|
||||
DTTERM_WindowManipulation = VTID("t"), // NOTE: Overlaps with DECSLPP. Fix when/if implemented.
|
||||
ANSISYSRC_CursorRestore = VTID("u"),
|
||||
DECREQTPARM_RequestTerminalParameters = VTID("x"),
|
||||
|
||||
@@ -241,9 +241,9 @@ void TermTelemetry::WriteFinalTraceLog() const
|
||||
TraceLoggingUInt32(_uiTimesUsed[DL], "DL"),
|
||||
TraceLoggingUInt32(_uiTimesUsed[SU], "SU"),
|
||||
TraceLoggingUInt32(_uiTimesUsed[SD], "SD"),
|
||||
TraceLoggingUInt32(_uiTimesUsed[ANSISYSSC], "ANSISYSSC"),
|
||||
TraceLoggingUInt32(_uiTimesUsed[ANSISYSRC], "ANSISYSRC"),
|
||||
TraceLoggingUInt32(_uiTimesUsed[DECSTBM], "DECSTBM"),
|
||||
TraceLoggingUInt32(_uiTimesUsed[DECSLRM], "DECSLRM"),
|
||||
TraceLoggingUInt32(_uiTimesUsed[NEL], "NEL"),
|
||||
TraceLoggingUInt32(_uiTimesUsed[IND], "IND"),
|
||||
TraceLoggingUInt32(_uiTimesUsed[RI], "RI"),
|
||||
|
||||
@@ -66,11 +66,11 @@ namespace Microsoft::Console::VirtualTerminal
|
||||
DCH,
|
||||
SU,
|
||||
SD,
|
||||
ANSISYSSC,
|
||||
ANSISYSRC,
|
||||
IL,
|
||||
DL,
|
||||
DECSTBM,
|
||||
DECSLRM,
|
||||
NEL,
|
||||
IND,
|
||||
RI,
|
||||
|
||||
@@ -1657,12 +1657,10 @@ class StateMachineExternalTest final
|
||||
|
||||
pDispatch->ClearState();
|
||||
|
||||
mach.ProcessCharacter(AsciiChars::ESC);
|
||||
mach.ProcessCharacter(L'[');
|
||||
mach.ProcessCharacter(L's');
|
||||
VERIFY_IS_TRUE(pDispatch->_cursorSave);
|
||||
|
||||
pDispatch->ClearState();
|
||||
// Note that CSI s is dispatched as SetLeftRightScrollingMargins rather
|
||||
// than CursorSaveState, so we don't test that here. The CursorSaveState
|
||||
// will only be triggered by this sequence (in AdaptDispatch) when the
|
||||
// Left-Right-Margin mode (DECLRMM) is disabled.
|
||||
|
||||
mach.ProcessCharacter(AsciiChars::ESC);
|
||||
mach.ProcessCharacter(L'[');
|
||||
|
||||
@@ -112,4 +112,7 @@ namespace Microsoft::Console::Utils
|
||||
// testing easier.
|
||||
std::wstring_view TrimPaste(std::wstring_view textView) noexcept;
|
||||
|
||||
// Same deal, but in TerminalPage::_evaluatePathForCwd
|
||||
std::wstring EvaluateStartingDirectory(std::wstring_view cwd, std::wstring_view startingDirectory);
|
||||
|
||||
}
|
||||
|
||||
@@ -33,6 +33,8 @@ class UtilsTests
|
||||
TEST_METHOD(TestTrimTrailingWhitespace);
|
||||
TEST_METHOD(TestDontTrimTrailingWhitespace);
|
||||
|
||||
TEST_METHOD(TestEvaluateStartingDirectory);
|
||||
|
||||
void _VerifyXTermColorResult(const std::wstring_view wstr, DWORD colorValue);
|
||||
void _VerifyXTermColorInvalid(const std::wstring_view wstr);
|
||||
};
|
||||
@@ -546,3 +548,65 @@ void UtilsTests::TestDontTrimTrailingWhitespace()
|
||||
// * trim when there's a tab followed by only whitespace
|
||||
// * not trim then there's a tab in the middle, and the string ends in whitespace
|
||||
}
|
||||
|
||||
void UtilsTests::TestEvaluateStartingDirectory()
|
||||
{
|
||||
// Continue on failures
|
||||
const WEX::TestExecution::DisableVerifyExceptions disableExceptionsScope;
|
||||
|
||||
auto test = [](auto& expected, auto& cwd, auto& startingDir) {
|
||||
VERIFY_ARE_EQUAL(expected, EvaluateStartingDirectory(cwd, startingDir));
|
||||
};
|
||||
|
||||
// A NOTE: EvaluateStartingDirectory makes no attempt to cannonicalize the
|
||||
// path. So if you do any sort of relative paths, it'll literally just
|
||||
// append.
|
||||
|
||||
{
|
||||
std::wstring cwd = L"C:\\Windows\\System32";
|
||||
|
||||
// Literally blank
|
||||
test(L"C:\\Windows\\System32\\", cwd, L"");
|
||||
|
||||
// Absolute Windows path
|
||||
test(L"C:\\Windows", cwd, L"C:\\Windows");
|
||||
test(L"C:/Users/migrie", cwd, L"C:/Users/migrie");
|
||||
|
||||
// Relative Windows path
|
||||
test(L"C:\\Windows\\System32\\.", cwd, L"."); // ?
|
||||
test(L"C:\\Windows\\System32\\.\\System32", cwd, L".\\System32"); // ?
|
||||
test(L"C:\\Windows\\System32\\./dev", cwd, L"./dev");
|
||||
|
||||
// WSL '~' path
|
||||
test(L"~", cwd, L"~");
|
||||
test(L"~/dev", cwd, L"~/dev");
|
||||
|
||||
// WSL or Windows / path - this will ultimately be evaluated by the connection
|
||||
test(L"/", cwd, L"/");
|
||||
test(L"/dev", cwd, L"/dev");
|
||||
}
|
||||
|
||||
{
|
||||
std::wstring cwd = L"C:/Users/migrie";
|
||||
|
||||
// Literally blank
|
||||
test(L"C:/Users/migrie\\", cwd, L"");
|
||||
|
||||
// Absolute Windows path
|
||||
test(L"C:\\Windows", cwd, L"C:\\Windows");
|
||||
test(L"C:/Users/migrie", cwd, L"C:/Users/migrie");
|
||||
|
||||
// Relative Windows path
|
||||
test(L"C:/Users/migrie\\.", cwd, L"."); // ?
|
||||
test(L"C:/Users/migrie\\.\\System32", cwd, L".\\System32"); // ?
|
||||
test(L"C:/Users/migrie\\./dev", cwd, L"./dev");
|
||||
|
||||
// WSL '~' path
|
||||
test(L"~", cwd, L"~");
|
||||
test(L"~/dev", cwd, L"~/dev");
|
||||
|
||||
// WSL or Windows / path - this will ultimately be evaluated by the connection
|
||||
test(L"/", cwd, L"/");
|
||||
test(L"/dev", cwd, L"/dev");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -729,7 +729,7 @@ std::tuple<std::wstring, std::wstring> Utils::MangleStartingDirectoryForWSL(std:
|
||||
const auto terminator{ commandLine.find_first_of(LR"(" )", 1) }; // look past the first character in case it starts with "
|
||||
const auto start{ til::at(commandLine, 0) == L'"' ? 1 : 0 };
|
||||
const std::filesystem::path executablePath{ commandLine.substr(start, terminator - start) };
|
||||
const auto executableFilename{ executablePath.filename().wstring() };
|
||||
const auto executableFilename{ executablePath.filename() };
|
||||
if (executableFilename == L"wsl" || executableFilename == L"wsl.exe")
|
||||
{
|
||||
// We've got a WSL -- let's just make sure it's the right one.
|
||||
@@ -784,7 +784,7 @@ std::tuple<std::wstring, std::wstring> Utils::MangleStartingDirectoryForWSL(std:
|
||||
}
|
||||
|
||||
return {
|
||||
fmt::format(LR"("{}" --cd "{}" {})", executablePath.wstring(), mangledDirectory, arguments),
|
||||
fmt::format(LR"("{}" --cd "{}" {})", executablePath.native(), mangledDirectory, arguments),
|
||||
std::wstring{}
|
||||
};
|
||||
}
|
||||
@@ -828,3 +828,27 @@ std::wstring_view Utils::TrimPaste(std::wstring_view textView) noexcept
|
||||
|
||||
return textView.substr(0, lastNonSpace + 1);
|
||||
}
|
||||
|
||||
std::wstring Utils::EvaluateStartingDirectory(
|
||||
std::wstring_view currentDirectory,
|
||||
std::wstring_view startingDirectory)
|
||||
{
|
||||
std::wstring resultPath{ startingDirectory };
|
||||
|
||||
// We only want to resolve the new WD against the CWD if it doesn't look
|
||||
// like a Linux path (see GH#592)
|
||||
|
||||
// Append only if it DOESN'T look like a linux-y path. A linux-y path starts
|
||||
// with `~` or `/`.
|
||||
const bool looksLikeLinux =
|
||||
resultPath.size() >= 1 &&
|
||||
(til::at(resultPath, 0) == L'~' || til::at(resultPath, 0) == L'/');
|
||||
|
||||
if (!looksLikeLinux)
|
||||
{
|
||||
std::filesystem::path cwd{ currentDirectory };
|
||||
cwd /= startingDirectory;
|
||||
resultPath = cwd.wstring();
|
||||
}
|
||||
return resultPath;
|
||||
}
|
||||
|
||||
@@ -81,7 +81,7 @@ static wchar_t* _ConsoleHostPath()
|
||||
// We tried the architecture infix version and failed, fall back to conhost.
|
||||
return _InboxConsoleHostPath();
|
||||
}
|
||||
auto modulePathAsString{ modulePath.wstring() };
|
||||
const auto& modulePathAsString = modulePath.native();
|
||||
return wil::make_process_heap_string_nothrow(modulePathAsString.data(), modulePathAsString.size());
|
||||
#endif // __INSIDE_WINDOWS
|
||||
}();
|
||||
|
||||
@@ -32,6 +32,10 @@ if (%1) == (rel) (
|
||||
echo Manually building release
|
||||
set _LAST_BUILD_CONF=Release
|
||||
)
|
||||
if (%1) == (audit) (
|
||||
echo Manually building audit mode
|
||||
set _LAST_BUILD_CONF=AuditMode
|
||||
)
|
||||
if (%1) == (no_clean) (
|
||||
set _MSBUILD_TARGET=Build
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user