Merge remote-tracking branch 'origin/main' into dev/migrie/oop/ragnarok

This commit is contained in:
Mike Griese 2021-10-28 08:49:38 -05:00
commit 51486a4168
35 changed files with 585 additions and 1091 deletions

View file

@ -67,6 +67,7 @@ SUMS$
^src/terminal/parser/ft_fuzzer/run\.bat$
^src/terminal/parser/ft_fuzzer/VTCommandFuzzer\.cpp$
^src/terminal/parser/ft_fuzzwrapper/run\.bat$
^src/terminal/parser/ut_parser/Base64Test.cpp$
^src/terminal/parser/ut_parser/run\.bat$
^src/tools/integrity/packageuwp/ConsoleUWP\.appxSources$
^src/tools/lnkd/lnkd\.bat$

View file

@ -938,6 +938,7 @@ GTP
guc
gui
guidatom
guiddef
GValue
GWL
GWLP
@ -1535,6 +1536,7 @@ NOCOLOR
NOCOMM
NOCONTEXTHELP
NOCOPYBITS
nodefaultlib
nodiscard
NODUP
noexcept
@ -2232,6 +2234,7 @@ STARTWPARMSW
Statusline
stdafx
STDAPI
stdc
stdcall
stdcpp
stderr

View file

@ -2,7 +2,7 @@
## Overview
This document outlines the roadmap towards delivering Windows Terminal 2.0 by Winter 2021.
This document outlines the roadmap towards delivering Windows Terminal 2.0.
## Milestones
@ -32,8 +32,8 @@ Below is the schedule for when milestones will be included in release builds of
| 2021-07-14 | [1.10] in Windows Terminal Preview<br>[1.9] in Windows Terminal | [Windows Terminal Preview 1.10 Release](https://devblogs.microsoft.com/commandline/windows-terminal-preview-1-10-release/) |
| 2021-08-31 | [1.11] in Windows Terminal Preview<br>[1.10] in Windows Terminal | [Windows Terminal Preview 1.11 Release](https://devblogs.microsoft.com/commandline/windows-terminal-preview-1-11-release/) |
| 2021-10-20 | [1.12] in Windows Terminal Preview<br>[1.11] in Windows Terminal | [Windows Terminal Preview 1.12 Release](https://devblogs.microsoft.com/commandline/windows-terminal-preview-1-12-release/) |
| 2021-11-30 | 2.0 RC in Windows Terminal Preview<br>2.0 RC in Windows Terminal | |
| 2021-12-31 | [2.0] in Windows Terminal Preview<br>[2.0] in Windows Terminal | |
| | 2.0 RC in Windows Terminal Preview<br>2.0 RC in Windows Terminal | |
| | [2.0] in Windows Terminal Preview<br>[2.0] in Windows Terminal | |
## Issue Triage & Prioritization
@ -49,28 +49,32 @@ The following are a list of the key scenarios we're aiming to deliver for Termin
> 👉 Note: There are many other features that don't fit within 2.0, but will be re-assessed and prioritized for 3.0, the plan for which will be published in 2021.
| Priority\* | Scenario | Description/Notes |
| ---------- | -------- | ----------------- |
| 0 | Settings UI | A user interface that connects to settings.json. This provides a way for people to edit their settings without having to edit a JSON file.<br><br>Issue: [#1564]<br>Specs: [#6720], [#6904]<br>Implementation: [#7283], [#7370], [#8048] |
| 0 | Command palette | A popup menu to list possible actions and commands.<br><br>Issues: [#5400], [#2046]<br>Spec: [#2193]<br>Implementation: [#6635] |
| 1 | Tab tear-off | The ability to tear a tab out of the current window and spawn a new window or attach it to a separate window.<br><br>Issue: [#1256], [#5000]<br>Spec: [#2080], [#7240] |
| 1 | Clickable links | Hyperlinking any links that appear in the text buffer. When clicking on the link, the link will open in your default browser.<br><br>Issue: [#574]<br>Implementation: [#7251] |
| 1 | Default terminal | If a command-line application is spawned, it should open in Windows Terminal (if installed) or your preferred terminal<br><br>Issue: [#492]<br>Spec: [#2080], [#7414] |
| 1 | Overall theme support | Tab coloring, title bar coloring, pane border coloring, pane border width, definition of what makes a theme<br><br>Issue: [#3327]<br>Spec: [#5772] |
| 1 | Open profile elevated | Configure profiles to always open elevated (if Terminal was run unelevated)<br><br>Issue: [#5000], [#632]<br>Spec: [#8455] |
| 1 | Open tab in existing window | Open new tabs in existing Terminal windows<br><br>Issue: [#5000], [#4472]<br>Spec: [#8135] |
| 1 | Traditional opacity | Have a transparent background without the acrylic blur.<br><br>Issue: [#603] <br>**Current State**: Blocked on WinUI 3.0 |
| 2 | SnapOnOutput, scroll lock | Pause output or scrolling on click.<br><br>Issue: [#980]<br>Spec: [#2529]<br>Implementation: [#6062] |
| 2 | Infinite scrollback | Have an infinite history for the text buffer.<br><br>Issue: [#1410] |
| 2 | Pane management | All issues listed out in the original issue. Some features include pane resizing with mouse, pane zooming, and opening a pane by prompting which profile to use.<br><br>Issue: [#1000] |
| 2 | Theme marketplace | Marketplace for creation and distribution of themes.<br>Dependent on overall theming |
| 2 | Jump list | Show profiles from task bar (on right click)/start menu.<br><br>Issue: [#576]<br>Implementation: [#7515] |
| 2 | Open with multiple tabs | A setting that allows Windows Terminal to launch with a specific tab configuration (not using only command line arguments).<br><br>Issue: [#756] |
| 3 | Open in Windows Terminal | Functionality to right click on a file or folder and select Open in Windows Terminal.<br><br>Issue: [#1060]<br>Implementation: [#6100] |
| 3 | Session restoration | Launch Windows Terminal and the previous session is restored with the proper tab and pane configuration and starting directories.<br><br>Issues: [#961], [#960], [#766] |
| 3 | Quake mode | Provide a quick launch terminal that appears and disappears when a hotkey is pressed.<br><br>Issue: [#653] |
| 3 | Settings migration infrastructure | Migrate people's settings without breaking them. Hand-in-hand with settings UI. |
| 3 | Pointer bindings | Provide settings that can be bound to the mouse.<br><br>Issue: [#1553] |
| Priority\* | Scenario | Description/Notes | State |
| ---------- | -------- | ----------------- | ----- |
| 0 | Settings UI | A user interface that connects to settings.json. This provides a way for people to edit their settings without having to edit a JSON file.<br><br>Issue: [#1564]<br>Specs: [#6720], [#6904]<br>Implementation: [#7283], [#7370], [#8048] | ✔️ |
| 0 | Command palette | A popup menu to list possible actions and commands.<br><br>Issues: [#5400], [#2046]<br>Spec: [#2193]<br>Implementation: [#6635] | ✔️ |
| 1 | Tab tear-off | The ability to tear a tab out of the current window and spawn a new window or attach it to a separate window.<br><br>Issue: [#1256], [#5000]<br>Spec: [#2080], [#7240] | 📝 |
| 1 | Clickable links | Hyperlinking any links that appear in the text buffer. When clicking on the link, the link will open in your default browser.<br><br>Issue: [#574]<br>Implementation: [#7251] | ✔️ |
| 1 | Default terminal | If a command-line application is spawned, it should open in Windows Terminal (if installed) or your preferred terminal<br><br>Issue: [#492]<br>Spec: [#2080], [#7414] | ✔️ |
| 1 | Overall theme support | Tab coloring, title bar coloring, pane border coloring, pane border width, definition of what makes a theme<br><br>Issue: [#3327]<br>Spec: [#5772] | 🦶 |
| 1 | Open profile elevated | Configure profiles to always open elevated (if Terminal was run unelevated)<br><br>Issue: [#5000], [#632]<br>Spec: [#8455] | 📝 |
| 1 | Open tab in existing window | Open new tabs in existing Terminal windows<br><br>Issue: [#5000], [#4472]<br>Spec: [#8135] | ✔️ |
| 1 | Traditional opacity | Have a transparent background without the acrylic blur.<br><br>Issue: [#603] | ✔️ |
| 2 | SnapOnOutput, scroll lock | Pause output or scrolling on click.<br><br>Issue: [#980]<br>Spec: [#2529]<br>Implementation: [#6062] | ✔️ |
| 2 | Infinite scrollback | Have an infinite history for the text buffer.<br><br>Issue: [#1410] | 🦶 |
| 2 | Pane management | All issues listed out in the original issue. Some features include pane resizing with mouse, pane zooming, and opening a pane by prompting which profile to use.<br><br>Issue: [#1000] | 📝 |
| 2 | Theme marketplace | Marketplace for creation and distribution of themes.<br>Dependent on overall theming | 🦶 |
| 2 | Jump list | Show profiles from task bar (on right click)/start menu.<br><br>Issue: [#576]<br>Implementation: [#7515] | ✔️ |
| 2 | Open with multiple tabs | A setting that allows Windows Terminal to launch with a specific tab configuration (not using only command line arguments).<br><br>Issue: [#756] | ✔️ |
| 3 | Open in Windows Terminal | Functionality to right click on a file or folder and select Open in Windows Terminal.<br><br>Issue: [#1060]<br>Implementation: [#6100] | ✔️ |
| 3 | Session restoration | Launch Windows Terminal and the previous session is restored with the proper tab and pane configuration and starting directories.<br><br>Issues: [#961], [#960], [#766] | ✔️ |
| 3 | Quake mode | Provide a quick launch terminal that appears and disappears when a hotkey is pressed.<br><br>Issue: [#653] | ✔️ |
| 3 | Settings migration infrastructure | Migrate people's settings without breaking them. Hand-in-hand with settings UI. | 🦶 |
| 3 | Pointer bindings | Provide settings that can be bound to the mouse.<br><br>Issue: [#1553] | 🦶 |
* 📝: The feature is currently in progress
* ✔️: The feature is complete and shipped in a Preview build
* 🦶: The feature is at risk of being punted to a future release cycle (beyond 2.0)
Feature Notes:

View file

@ -7,7 +7,9 @@
#include "../TerminalSettingsModel/CascadiaSettings.h"
#include "JsonTestClass.h"
#include "TestUtils.h"
#include <defaults.h>
#include <userDefaults.h>
using namespace Microsoft::Console;
using namespace WEX::Logging;
@ -70,6 +72,7 @@ namespace SettingsModelLocalTests
TEST_METHOD(TestCloneInheritanceTree);
TEST_METHOD(TestValidDefaults);
TEST_METHOD(TestInheritedCommand);
TEST_METHOD(LoadFragmentsWithMultipleUpdates);
private:
static winrt::com_ptr<implementation::CascadiaSettings> createSettings(const std::string_view& userJSON)
@ -1979,4 +1982,34 @@ namespace SettingsModelLocalTests
VERIFY_IS_NULL(actualKeyChord);
}
}
// This test ensures GH#11597 doesn't regress.
void DeserializationTests::LoadFragmentsWithMultipleUpdates()
{
static constexpr std::wstring_view fragmentSource{ L"fragment" };
static constexpr std::string_view fragmentJson{ R"({
"profiles": [
{
"updates": "{61c54bbd-c2c6-5271-96e7-009a87ff44bf}",
"cursorShape": "filledBox"
},
{
"updates": "{0caa0dad-35be-5f56-a8ff-afceeeaa6101}",
"cursorShape": "filledBox"
},
{
"guid": "{6239a42c-0000-49a3-80bd-e8fdd045185c}",
"commandline": "cmd.exe"
}
]
})" };
implementation::SettingsLoader loader{ std::string_view{}, DefaultJson };
loader.MergeInboxIntoUserSettings();
loader.MergeFragmentIntoUserSettings(winrt::hstring{ fragmentSource }, fragmentJson);
loader.FinalizeLayering();
VERIFY_IS_FALSE(loader.duplicateProfile);
VERIFY_ARE_EQUAL(3u, loader.userSettings.profiles.size());
}
}

View file

@ -15,6 +15,7 @@
<Grid x:Name="Root"
Background="Transparent">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
@ -22,8 +23,50 @@
<local:TabRowControl x:Name="TabRow"
Grid.Row="0" />
<StackPanel Grid.Row="1">
<mux:InfoBar x:Name="KeyboardServiceWarningInfoBar"
x:Load="False"
IsClosable="True"
IsIconVisible="True"
IsOpen="False"
Message="{x:Bind KeyboardServiceDisabledText, Mode=OneWay}"
Severity="Warning">
<mux:InfoBar.ActionButton>
<Button x:Uid="InfoBarDismissButton"
Click="_KeyboardServiceWarningInfoDismissHandler" />
</mux:InfoBar.ActionButton>
</mux:InfoBar>
<mux:InfoBar x:Name="CloseOnExitInfoBar"
x:Uid="CloseOnExitInfoBar"
x:Load="False"
IsClosable="True"
IsIconVisible="True"
IsOpen="False"
Severity="Informational">
<mux:InfoBar.ActionButton>
<Button x:Uid="InfoBarDismissButton"
Click="_CloseOnExitInfoDismissHandler" />
</mux:InfoBar.ActionButton>
</mux:InfoBar>
<mux:InfoBar x:Name="SetAsDefaultInfoBar"
x:Uid="SetAsDefaultInfoBar"
x:Load="False"
CloseButtonClick="_SetAsDefaultDismissHandler"
IsClosable="True"
IsIconVisible="True"
IsOpen="False"
Severity="Informational">
<mux:InfoBar.ActionButton>
<HyperlinkButton x:Uid="SetAsDefaultTip_OpenSettingsLink"
Click="_SetAsDefaultOpenSettingsHandler" />
</mux:InfoBar.ActionButton>
</mux:InfoBar>
</StackPanel>
<Grid x:Name="TabContent"
Grid.Row="1"
Grid.Row="2"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch" />
@ -115,48 +158,6 @@
PreviewKeyDown="_KeyDownHandler"
Visibility="Collapsed" />
<StackPanel>
<mux:InfoBar x:Name="KeyboardServiceWarningInfoBar"
x:Load="False"
IsClosable="True"
IsIconVisible="True"
IsOpen="False"
Message="{x:Bind KeyboardServiceDisabledText, Mode=OneWay}"
Severity="Warning">
<mux:InfoBar.ActionButton>
<Button x:Uid="InfoBarDismissButton"
Click="_KeyboardServiceWarningInfoDismissHandler" />
</mux:InfoBar.ActionButton>
</mux:InfoBar>
<mux:InfoBar x:Name="CloseOnExitInfoBar"
x:Uid="CloseOnExitInfoBar"
x:Load="False"
IsClosable="True"
IsIconVisible="True"
IsOpen="False"
Severity="Informational">
<mux:InfoBar.ActionButton>
<Button x:Uid="InfoBarDismissButton"
Click="_CloseOnExitInfoDismissHandler" />
</mux:InfoBar.ActionButton>
</mux:InfoBar>
<mux:InfoBar x:Name="SetAsDefaultInfoBar"
x:Uid="SetAsDefaultInfoBar"
x:Load="False"
CloseButtonClick="_SetAsDefaultDismissHandler"
IsClosable="True"
IsIconVisible="True"
IsOpen="False"
Severity="Informational">
<mux:InfoBar.ActionButton>
<HyperlinkButton x:Uid="SetAsDefaultTip_OpenSettingsLink"
Click="_SetAsDefaultOpenSettingsHandler" />
</mux:InfoBar.ActionButton>
</mux:InfoBar>
</StackPanel>
<!--
A TeachingTip with IsLightDismissEnabled="True" will immediately
dismiss itself if the window is unfocused (In Xaml Islands). This is

View file

@ -3,6 +3,7 @@
#pragma once
#include "../../terminal/adapter/DispatchTypes.hpp"
#include "../../terminal/input/terminalInput.hpp"
#include "../../buffer/out/TextAttribute.hpp"
#include "../../types/inc/Viewport.hpp"
@ -47,16 +48,9 @@ namespace Microsoft::Terminal::Core
virtual bool SetDefaultForeground(const DWORD color) noexcept = 0;
virtual bool SetDefaultBackground(const DWORD color) noexcept = 0;
virtual bool EnableWin32InputMode(const bool win32InputMode) noexcept = 0;
virtual bool SetCursorKeysMode(const bool applicationMode) noexcept = 0;
virtual bool SetKeypadMode(const bool applicationMode) noexcept = 0;
virtual bool SetInputMode(const ::Microsoft::Console::VirtualTerminal::TerminalInput::Mode mode, const bool enabled) noexcept = 0;
virtual bool SetScreenMode(const bool reverseMode) noexcept = 0;
virtual bool EnableVT200MouseMode(const bool enabled) noexcept = 0;
virtual bool EnableUTF8ExtendedMouseMode(const bool enabled) noexcept = 0;
virtual bool EnableSGRExtendedMouseMode(const bool enabled) noexcept = 0;
virtual bool EnableButtonEventMouseMode(const bool enabled) noexcept = 0;
virtual bool EnableAnyEventMouseMode(const bool enabled) noexcept = 0;
virtual bool EnableAlternateScrollMode(const bool enabled) noexcept = 0;
virtual bool EnableXtermBracketedPasteMode(const bool enabled) noexcept = 0;
virtual bool IsXtermBracketedPasteModeEnabled() const = 0;

View file

@ -113,16 +113,9 @@ public:
bool SetDefaultForeground(const COLORREF color) noexcept override;
bool SetDefaultBackground(const COLORREF color) noexcept override;
bool EnableWin32InputMode(const bool win32InputMode) noexcept override;
bool SetCursorKeysMode(const bool applicationMode) noexcept override;
bool SetKeypadMode(const bool applicationMode) noexcept override;
bool SetInputMode(const ::Microsoft::Console::VirtualTerminal::TerminalInput::Mode mode, const bool enabled) noexcept override;
bool SetScreenMode(const bool reverseMode) noexcept override;
bool EnableVT200MouseMode(const bool enabled) noexcept override;
bool EnableUTF8ExtendedMouseMode(const bool enabled) noexcept override;
bool EnableSGRExtendedMouseMode(const bool enabled) noexcept override;
bool EnableButtonEventMouseMode(const bool enabled) noexcept override;
bool EnableAnyEventMouseMode(const bool enabled) noexcept override;
bool EnableAlternateScrollMode(const bool enabled) noexcept override;
bool EnableXtermBracketedPasteMode(const bool enabled) noexcept override;
bool IsXtermBracketedPasteModeEnabled() const noexcept override;

View file

@ -482,23 +482,13 @@ til::color Terminal::GetDefaultBackground() const noexcept
return _defaultBg;
}
bool Terminal::EnableWin32InputMode(const bool win32InputMode) noexcept
bool Terminal::SetInputMode(const TerminalInput::Mode mode, const bool enabled) noexcept
try
{
_terminalInput->ChangeWin32InputMode(win32InputMode);
return true;
}
bool Terminal::SetCursorKeysMode(const bool applicationMode) noexcept
{
_terminalInput->ChangeCursorKeysMode(applicationMode);
return true;
}
bool Terminal::SetKeypadMode(const bool applicationMode) noexcept
{
_terminalInput->ChangeKeypadMode(applicationMode);
_terminalInput->SetInputMode(mode, enabled);
return true;
}
CATCH_RETURN_FALSE()
bool Terminal::SetScreenMode(const bool reverseMode) noexcept
try
@ -511,42 +501,6 @@ try
}
CATCH_RETURN_FALSE()
bool Terminal::EnableVT200MouseMode(const bool enabled) noexcept
{
_terminalInput->EnableDefaultTracking(enabled);
return true;
}
bool Terminal::EnableUTF8ExtendedMouseMode(const bool enabled) noexcept
{
_terminalInput->SetUtf8ExtendedMode(enabled);
return true;
}
bool Terminal::EnableSGRExtendedMouseMode(const bool enabled) noexcept
{
_terminalInput->SetSGRExtendedMode(enabled);
return true;
}
bool Terminal::EnableButtonEventMouseMode(const bool enabled) noexcept
{
_terminalInput->EnableButtonEventTracking(enabled);
return true;
}
bool Terminal::EnableAnyEventMouseMode(const bool enabled) noexcept
{
_terminalInput->EnableAnyEventTracking(enabled);
return true;
}
bool Terminal::EnableAlternateScrollMode(const bool enabled) noexcept
{
_terminalInput->EnableAlternateScroll(enabled);
return true;
}
bool Terminal::EnableXtermBracketedPasteMode(const bool enabled) noexcept
{
_bracketedPasteMode = enabled;

View file

@ -321,9 +321,9 @@ CATCH_LOG_RETURN_FALSE()
// - applicationMode - set to true to enable Application Mode Input, false for Numeric Mode Input.
// Return Value:
// - True if handled successfully. False otherwise.
bool TerminalDispatch::SetKeypadMode(const bool fApplicationMode) noexcept
bool TerminalDispatch::SetKeypadMode(const bool applicationMode) noexcept
{
_terminalApi.SetKeypadMode(fApplicationMode);
_terminalApi.SetInputMode(TerminalInput::Mode::Keypad, applicationMode);
return true;
}
@ -334,7 +334,7 @@ bool TerminalDispatch::SetKeypadMode(const bool fApplicationMode) noexcept
// - True if handled successfully. False otherwise.
bool TerminalDispatch::SetCursorKeysMode(const bool applicationMode) noexcept
{
_terminalApi.SetCursorKeysMode(applicationMode);
_terminalApi.SetInputMode(TerminalInput::Mode::CursorKey, applicationMode);
return true;
}
@ -359,7 +359,7 @@ bool TerminalDispatch::SetScreenMode(const bool reverseMode) noexcept
// - True if handled successfully. False otherwise.
bool TerminalDispatch::EnableWin32InputMode(const bool win32Mode) noexcept
{
_terminalApi.EnableWin32InputMode(win32Mode);
_terminalApi.SetInputMode(TerminalInput::Mode::Win32, win32Mode);
return true;
}
@ -371,7 +371,7 @@ bool TerminalDispatch::EnableWin32InputMode(const bool win32Mode) noexcept
// True if handled successfully. False otherwise.
bool TerminalDispatch::EnableVT200MouseMode(const bool enabled) noexcept
{
_terminalApi.EnableVT200MouseMode(enabled);
_terminalApi.SetInputMode(TerminalInput::Mode::DefaultMouseTracking, enabled);
return true;
}
@ -384,7 +384,7 @@ bool TerminalDispatch::EnableVT200MouseMode(const bool enabled) noexcept
// True if handled successfully. False otherwise.
bool TerminalDispatch::EnableUTF8ExtendedMouseMode(const bool enabled) noexcept
{
_terminalApi.EnableUTF8ExtendedMouseMode(enabled);
_terminalApi.SetInputMode(TerminalInput::Mode::Utf8MouseEncoding, enabled);
return true;
}
@ -397,7 +397,7 @@ bool TerminalDispatch::EnableUTF8ExtendedMouseMode(const bool enabled) noexcept
// True if handled successfully. False otherwise.
bool TerminalDispatch::EnableSGRExtendedMouseMode(const bool enabled) noexcept
{
_terminalApi.EnableSGRExtendedMouseMode(enabled);
_terminalApi.SetInputMode(TerminalInput::Mode::SgrMouseEncoding, enabled);
return true;
}
@ -409,7 +409,7 @@ bool TerminalDispatch::EnableSGRExtendedMouseMode(const bool enabled) noexcept
// True if handled successfully. False otherwise.
bool TerminalDispatch::EnableButtonEventMouseMode(const bool enabled) noexcept
{
_terminalApi.EnableButtonEventMouseMode(enabled);
_terminalApi.SetInputMode(TerminalInput::Mode::ButtonEventMouseTracking, enabled);
return true;
}
@ -422,7 +422,7 @@ bool TerminalDispatch::EnableButtonEventMouseMode(const bool enabled) noexcept
// True if handled successfully. False otherwise.
bool TerminalDispatch::EnableAnyEventMouseMode(const bool enabled) noexcept
{
_terminalApi.EnableAnyEventMouseMode(enabled);
_terminalApi.SetInputMode(TerminalInput::Mode::AnyEventMouseTracking, enabled);
return true;
}
@ -435,7 +435,7 @@ bool TerminalDispatch::EnableAnyEventMouseMode(const bool enabled) noexcept
// True if handled successfully. False otherwise.
bool TerminalDispatch::EnableAlternateScroll(const bool enabled) noexcept
{
_terminalApi.EnableAlternateScrollMode(enabled);
_terminalApi.SetInputMode(TerminalInput::Mode::AlternateScroll, enabled);
return true;
}

View file

@ -57,6 +57,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
void ApplyRuntimeInitialSettings();
void MergeInboxIntoUserSettings();
void FindFragmentsAndMergeIntoUserSettings();
void MergeFragmentIntoUserSettings(const winrt::hstring& source, const std::string_view& content);
void FinalizeLayering();
bool DisableDeletedProfiles();
@ -82,7 +83,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
void _parseFragment(const winrt::hstring& source, const std::string_view& content, ParsedSettings& settings);
static JsonSettings _parseJson(const std::string_view& content);
static winrt::com_ptr<implementation::Profile> _parseProfile(const OriginTag origin, const winrt::hstring& source, const Json::Value& profileJson);
void _appendProfile(winrt::com_ptr<implementation::Profile>&& profile, ParsedSettings& settings);
void _appendProfile(winrt::com_ptr<Profile>&& profile, const winrt::guid& guid, ParsedSettings& settings);
static void _addParentProfile(const winrt::com_ptr<implementation::Profile>& profile, ParsedSettings& settings);
void _executeGenerator(const IDynamicProfileGenerator& generator);

View file

@ -207,26 +207,6 @@ void SettingsLoader::FindFragmentsAndMergeIntoUserSettings()
{
const auto content = ReadUTF8File(fragmentExt.path());
_parseFragment(source, content, fragmentSettings);
for (const auto& fragmentProfile : fragmentSettings.profiles)
{
if (const auto updates = fragmentProfile->Updates(); updates != winrt::guid{})
{
if (const auto it = userSettings.profilesByGuid.find(updates); it != userSettings.profilesByGuid.end())
{
it->second->InsertParent(0, fragmentProfile);
}
}
else
{
_addParentProfile(fragmentProfile, userSettings);
}
}
for (const auto& kv : fragmentSettings.globals->ColorSchemes())
{
userSettings.globals->AddColorScheme(kv.Value());
}
}
CATCH_LOG();
}
@ -289,6 +269,15 @@ void SettingsLoader::FindFragmentsAndMergeIntoUserSettings()
}
}
// See FindFragmentsAndMergeIntoUserSettings.
// This function does the same, but for a single given JSON blob and source
// and at the time of writing is used for unit tests only.
void SettingsLoader::MergeFragmentIntoUserSettings(const winrt::hstring& source, const std::string_view& content)
{
ParsedSettings fragmentSettings;
_parseFragment(source, content, fragmentSettings);
}
// Call this method before passing SettingsLoader to the CascadiaSettings constructor.
// It layers all remaining objects onto each other (those that aren't covered
// by MergeInboxIntoUserSettings/FindFragmentsAndMergeIntoUserSettings).
@ -475,7 +464,7 @@ void SettingsLoader::_parse(const OriginTag origin, const winrt::hstring& source
// GH#9962: Discard Guid-less, Name-less profiles.
if (profile->HasGuid())
{
_appendProfile(std::move(profile), settings);
_appendProfile(std::move(profile), profile->Guid(), settings);
}
}
}
@ -517,14 +506,37 @@ void SettingsLoader::_parseFragment(const winrt::hstring& source, const std::str
auto profile = _parseProfile(OriginTag::Fragment, source, profileJson);
// GH#9962: Discard Guid-less, Name-less profiles, but...
// allow ones with an Updates field, as those are special for fragments.
if (profile->HasGuid() || profile->Updates() != winrt::guid{})
// We need to make sure to only call Guid() if HasGuid() is true,
// as Guid() will dynamically generate a return value otherwise.
const auto guid = profile->HasGuid() ? profile->Guid() : profile->Updates();
if (guid != winrt::guid{})
{
_appendProfile(std::move(profile), settings);
_appendProfile(std::move(profile), guid, settings);
}
}
CATCH_LOG()
}
}
for (const auto& fragmentProfile : settings.profiles)
{
if (const auto updates = fragmentProfile->Updates(); updates != winrt::guid{})
{
if (const auto it = userSettings.profilesByGuid.find(updates); it != userSettings.profilesByGuid.end())
{
it->second->InsertParent(0, fragmentProfile);
}
}
else
{
_addParentProfile(fragmentProfile, userSettings);
}
}
for (const auto& kv : settings.globals->ColorSchemes())
{
userSettings.globals->AddColorScheme(kv.Value());
}
}
SettingsLoader::JsonSettings SettingsLoader::_parseJson(const std::string_view& content)
@ -564,11 +576,11 @@ winrt::com_ptr<Profile> SettingsLoader::_parseProfile(const OriginTag origin, co
// Adds a profile to the ParsedSettings instance. Takes ownership of the profile.
// It ensures no duplicate GUIDs are added to the ParsedSettings instance.
void SettingsLoader::_appendProfile(winrt::com_ptr<Profile>&& profile, ParsedSettings& settings)
void SettingsLoader::_appendProfile(winrt::com_ptr<Profile>&& profile, const winrt::guid& guid, ParsedSettings& settings)
{
// FYI: The static_cast ensures we don't move the profile into
// `profilesByGuid`, even though we still need it later for `profiles`.
if (settings.profilesByGuid.emplace(profile->Guid(), static_cast<const winrt::com_ptr<Profile>&>(profile)).second)
if (settings.profilesByGuid.emplace(guid, static_cast<const winrt::com_ptr<Profile>&>(profile)).second)
{
settings.profiles.emplace_back(profile);
}

View file

@ -107,7 +107,7 @@
C4467: usage of ATL attributes is deprecated
Conhost code still uses ATL.
-->
<DisableSpecificWarnings>4103;4201;4312;4467;%(DisableSpecificWarnings)</DisableSpecificWarnings>
<DisableSpecificWarnings>4103;4201;4312;4467;5105;%(DisableSpecificWarnings)</DisableSpecificWarnings>
<PreprocessorDefinitions>_WINDOWS;EXTERNAL_BUILD;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<SDLCheck>true</SDLCheck>
<PrecompiledHeaderFile>precomp.h</PrecompiledHeaderFile>
@ -117,6 +117,7 @@
<MinimalRebuild>false</MinimalRebuild>
<RuntimeTypeInfo>false</RuntimeTypeInfo>
<LanguageStandard>stdcpp17</LanguageStandard>
<LanguageStandard_C>stdc17</LanguageStandard_C>
<AdditionalOptions>/utf-8 %(AdditionalOptions)</AdditionalOptions>
<ControlFlowGuard>Guard</ControlFlowGuard>
<FloatingPointModel>Fast</FloatingPointModel>

View file

@ -75,7 +75,7 @@
<ConformanceMode>true</ConformanceMode>
<UseStandardPreprocessor>true</UseStandardPreprocessor>
<AdditionalOptions>%(AdditionalOptions) /bigobj /Zc:twoPhase-</AdditionalOptions>
<DisableSpecificWarnings>5104;5105;28204;%(DisableSpecificWarnings)</DisableSpecificWarnings>
<DisableSpecificWarnings>5104;28204;%(DisableSpecificWarnings)</DisableSpecificWarnings>
<AdditionalUsingDirectories>$(WindowsSDK_WindowsMetadata);$(AdditionalUsingDirectories)</AdditionalUsingDirectories>
</ClCompile>

View file

@ -1248,55 +1248,6 @@ void ApiRoutines::GetConsoleDisplayModeImpl(ULONG& flags) noexcept
CATCH_RETURN();
}
// Routine Description:
// - A private API call for changing the cursor keys input mode between normal and application mode.
// The cursor keys are the arrows, plus Home and End.
// Parameters:
// - fApplicationMode - set to true to enable Application Mode Input, false for Numeric Mode Input.
// Return value:
// - True if handled successfully. False otherwise.
[[nodiscard]] NTSTATUS DoSrvPrivateSetCursorKeysMode(_In_ bool fApplicationMode)
{
CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
if (gci.pInputBuffer == nullptr)
{
return STATUS_UNSUCCESSFUL;
}
gci.pInputBuffer->GetTerminalInput().ChangeCursorKeysMode(fApplicationMode);
return STATUS_SUCCESS;
}
// Routine Description:
// - A private API call for changing the keypad input mode between numeric and application mode.
// This controls what the keys on the numpad translate to.
// Parameters:
// - fApplicationMode - set to true to enable Application Mode Input, false for Numeric Mode Input.
// Return value:
// - True if handled successfully. False otherwise.
[[nodiscard]] NTSTATUS DoSrvPrivateSetKeypadMode(_In_ bool fApplicationMode)
{
CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
if (gci.pInputBuffer == nullptr)
{
return STATUS_UNSUCCESSFUL;
}
gci.pInputBuffer->GetTerminalInput().ChangeKeypadMode(fApplicationMode);
return STATUS_SUCCESS;
}
// Function Description:
// - A private API call which enables/disables sending full input records
// encoded as a string of characters to the client application.
// Parameters:
// - win32InputMode - set to true to enable win32-input-mode, false to disable.
// Return value:
// - <none>
void DoSrvPrivateEnableWin32InputMode(const bool win32InputMode)
{
CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
gci.pInputBuffer->GetTerminalInput().ChangeWin32InputMode(win32InputMode);
}
// Routine Description:
// - A private API call for changing the screen mode between normal and reverse.
// When in reverse screen mode, the background and foreground colors are switched.
@ -1525,78 +1476,6 @@ void DoSrvPrivateUseMainScreenBuffer(SCREEN_INFORMATION& screenInfo)
screenInfo.GetActiveBuffer().UseMainScreenBuffer();
}
// Routine Description:
// - A private API call for enabling VT200 style mouse mode.
// Parameters:
// - fEnable - true to enable default tracking mode, false to disable mouse mode.
// Return value:
// - None
void DoSrvPrivateEnableVT200MouseMode(const bool fEnable)
{
CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
gci.GetActiveInputBuffer()->GetTerminalInput().EnableDefaultTracking(fEnable);
}
// Routine Description:
// - A private API call for enabling utf8 style mouse mode.
// Parameters:
// - fEnable - true to enable, false to disable.
// Return value:
// - None
void DoSrvPrivateEnableUTF8ExtendedMouseMode(const bool fEnable)
{
CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
gci.GetActiveInputBuffer()->GetTerminalInput().SetUtf8ExtendedMode(fEnable);
}
// Routine Description:
// - A private API call for enabling SGR style mouse mode.
// Parameters:
// - fEnable - true to enable, false to disable.
// Return value:
// - None
void DoSrvPrivateEnableSGRExtendedMouseMode(const bool fEnable)
{
CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
gci.GetActiveInputBuffer()->GetTerminalInput().SetSGRExtendedMode(fEnable);
}
// Routine Description:
// - A private API call for enabling button-event mouse mode.
// Parameters:
// - fEnable - true to enable button-event mode, false to disable mouse mode.
// Return value:
// - None
void DoSrvPrivateEnableButtonEventMouseMode(const bool fEnable)
{
CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
gci.GetActiveInputBuffer()->GetTerminalInput().EnableButtonEventTracking(fEnable);
}
// Routine Description:
// - A private API call for enabling any-event mouse mode.
// Parameters:
// - fEnable - true to enable any-event mode, false to disable mouse mode.
// Return value:
// - None
void DoSrvPrivateEnableAnyEventMouseMode(const bool fEnable)
{
CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
gci.GetActiveInputBuffer()->GetTerminalInput().EnableAnyEventTracking(fEnable);
}
// Routine Description:
// - A private API call for enabling alternate scroll mode
// Parameters:
// - fEnable - true to enable alternate scroll mode, false to disable.
// Return value:
// None
void DoSrvPrivateEnableAlternateScroll(const bool fEnable)
{
CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
gci.GetActiveInputBuffer()->GetTerminalInput().EnableAlternateScroll(fEnable);
}
// Routine Description:
// - A private API call for performing a VT-style erase all operation on the buffer.
// See SCREEN_INFORMATION::VtEraseAll's description for details.

View file

@ -18,10 +18,6 @@ Revision History:
#include "../inc/conattrs.hpp"
class SCREEN_INFORMATION;
[[nodiscard]] NTSTATUS DoSrvPrivateSetCursorKeysMode(_In_ bool fApplicationMode);
[[nodiscard]] NTSTATUS DoSrvPrivateSetKeypadMode(_In_ bool fApplicationMode);
void DoSrvPrivateEnableWin32InputMode(const bool win32InputMode);
[[nodiscard]] NTSTATUS DoSrvPrivateSetScreenMode(const bool reverseMode);
[[nodiscard]] NTSTATUS DoSrvPrivateSetAutoWrapMode(const bool wrapAtEOL);
@ -35,13 +31,6 @@ void DoSrvPrivateAllowCursorBlinking(SCREEN_INFORMATION& screenInfo, const bool
[[nodiscard]] NTSTATUS DoSrvPrivateUseAlternateScreenBuffer(SCREEN_INFORMATION& screenInfo);
void DoSrvPrivateUseMainScreenBuffer(SCREEN_INFORMATION& screenInfo);
void DoSrvPrivateEnableVT200MouseMode(const bool fEnable);
void DoSrvPrivateEnableUTF8ExtendedMouseMode(const bool fEnable);
void DoSrvPrivateEnableSGRExtendedMouseMode(const bool fEnable);
void DoSrvPrivateEnableButtonEventMouseMode(const bool fEnable);
void DoSrvPrivateEnableAnyEventMouseMode(const bool fEnable);
void DoSrvPrivateEnableAlternateScroll(const bool fEnable);
[[nodiscard]] HRESULT DoSrvPrivateEraseAll(SCREEN_INFORMATION& screenInfo);
[[nodiscard]] HRESULT DoSrvPrivateClearBuffer(SCREEN_INFORMATION& screenInfo);

View file

@ -15,6 +15,7 @@
using namespace Microsoft::Console;
using Microsoft::Console::Interactivity::ServiceLocator;
using Microsoft::Console::VirtualTerminal::TerminalInput;
WriteBuffer::WriteBuffer(_In_ Microsoft::Console::IIoProvider& io) :
_io{ io },
@ -237,43 +238,28 @@ bool ConhostInternalGetSet::SetConsoleWindowInfo(const bool absolute, const SMAL
}
// Routine Description:
// - Connects the PrivateSetCursorKeysMode call directly into our Driver Message servicing call inside Conhost.exe
// PrivateSetCursorKeysMode is an internal-only "API" call that the vt commands can execute,
// - Sets the various terminal input modes.
// SetInputMode is an internal-only "API" call that the vt commands can execute,
// but it is not represented as a function call on out public API surface.
// Arguments:
// - fApplicationMode - set to true to enable Application Mode Input, false for Normal Mode.
// - mode - the input mode to change.
// - enabled - set to true to enable the mode, false to disable it.
// Return Value:
// - true if successful (see DoSrvPrivateSetCursorKeysMode). false otherwise.
bool ConhostInternalGetSet::PrivateSetCursorKeysMode(const bool fApplicationMode)
// - true if successful. false otherwise.
bool ConhostInternalGetSet::SetInputMode(const TerminalInput::Mode mode, const bool enabled)
{
return NT_SUCCESS(DoSrvPrivateSetCursorKeysMode(fApplicationMode));
}
auto& terminalInput = _io.GetActiveInputBuffer()->GetTerminalInput();
terminalInput.SetInputMode(mode, enabled);
// Routine Description:
// - Connects the PrivateSetKeypadMode call directly into our Driver Message servicing call inside Conhost.exe
// PrivateSetKeypadMode is an internal-only "API" call that the vt commands can execute,
// but it is not represented as a function call on out public API surface.
// Arguments:
// - fApplicationMode - set to true to enable Application Mode Input, false for Numeric Mode.
// Return Value:
// - true if successful (see DoSrvPrivateSetKeypadMode). false otherwise.
bool ConhostInternalGetSet::PrivateSetKeypadMode(const bool fApplicationMode)
{
return NT_SUCCESS(DoSrvPrivateSetKeypadMode(fApplicationMode));
}
// Routine Description:
// - Connects the PrivateEnableWin32InputMode call directly into our Driver Message servicing call inside Conhost.exe
// PrivateEnableWin32InputMode is an internal-only "API" call that the vt commands can execute,
// but it is not represented as a function call on out public API surface.
// Arguments:
// - win32InputMode - set to true to enable win32-input-mode, false to disable.
// Return Value:
// - true always
bool ConhostInternalGetSet::PrivateEnableWin32InputMode(const bool win32InputMode)
{
DoSrvPrivateEnableWin32InputMode(win32InputMode);
return true;
// If we're a conpty, AND WE'RE IN VT INPUT MODE, always pass input mode requests
// The VT Input mode check is to work around ssh.exe v7.7, which uses VT
// output, but not Input.
// The original comment said, "Once the conpty supports these types of input,
// this check can be removed. See GH#4911". Unfortunately, time has shown
// us that SSH 7.7 _also_ requests mouse input and that can have a user interface
// impact on the actual connected terminal. We can't remove this check,
// because SSH <=7.7 is out in the wild on all versions of Windows <=2004.
return !(IsConsolePty() && PrivateIsVtInputEnabled());
}
// Routine Description:
@ -289,7 +275,7 @@ bool ConhostInternalGetSet::PrivateSetAnsiMode(const bool ansiMode)
auto& stateMachine = _io.GetActiveOutputBuffer().GetStateMachine();
stateMachine.SetAnsiMode(ansiMode);
auto& terminalInput = _io.GetActiveInputBuffer()->GetTerminalInput();
terminalInput.ChangeAnsiMode(ansiMode);
terminalInput.SetInputMode(TerminalInput::Mode::Ansi, ansiMode);
return true;
}
@ -448,90 +434,6 @@ bool ConhostInternalGetSet::PrivateUseMainScreenBuffer()
return true;
}
// Routine Description:
// - Connects the PrivateEnableVT200MouseMode call directly into our Driver Message servicing call inside Conhost.exe
// PrivateEnableVT200MouseMode is an internal-only "API" call that the vt commands can execute,
// but it is not represented as a function call on out public API surface.
// Arguments:
// - enabled - set to true to enable vt200 mouse mode, false to disable
// Return Value:
// - true if successful (see DoSrvPrivateEnableVT200MouseMode). false otherwise.
bool ConhostInternalGetSet::PrivateEnableVT200MouseMode(const bool enabled)
{
DoSrvPrivateEnableVT200MouseMode(enabled);
return true;
}
// Routine Description:
// - Connects the PrivateEnableUTF8ExtendedMouseMode call directly into our Driver Message servicing call inside Conhost.exe
// PrivateEnableUTF8ExtendedMouseMode is an internal-only "API" call that the vt commands can execute,
// but it is not represented as a function call on out public API surface.
// Arguments:
// - enabled - set to true to enable utf8 extended mouse mode, false to disable
// Return Value:
// - true if successful (see DoSrvPrivateEnableUTF8ExtendedMouseMode). false otherwise.
bool ConhostInternalGetSet::PrivateEnableUTF8ExtendedMouseMode(const bool enabled)
{
DoSrvPrivateEnableUTF8ExtendedMouseMode(enabled);
return true;
}
// Routine Description:
// - Connects the PrivateEnableSGRExtendedMouseMode call directly into our Driver Message servicing call inside Conhost.exe
// PrivateEnableSGRExtendedMouseMode is an internal-only "API" call that the vt commands can execute,
// but it is not represented as a function call on out public API surface.
// Arguments:
// - enabled - set to true to enable SGR extended mouse mode, false to disable
// Return Value:
// - true if successful (see DoSrvPrivateEnableSGRExtendedMouseMode). false otherwise.
bool ConhostInternalGetSet::PrivateEnableSGRExtendedMouseMode(const bool enabled)
{
DoSrvPrivateEnableSGRExtendedMouseMode(enabled);
return true;
}
// Routine Description:
// - Connects the PrivateEnableButtonEventMouseMode call directly into our Driver Message servicing call inside Conhost.exe
// PrivateEnableButtonEventMouseMode is an internal-only "API" call that the vt commands can execute,
// but it is not represented as a function call on out public API surface.
// Arguments:
// - enabled - set to true to enable button-event mouse mode, false to disable
// Return Value:
// - true if successful (see DoSrvPrivateEnableButtonEventMouseMode). false otherwise.
bool ConhostInternalGetSet::PrivateEnableButtonEventMouseMode(const bool enabled)
{
DoSrvPrivateEnableButtonEventMouseMode(enabled);
return true;
}
// Routine Description:
// - Connects the PrivateEnableAnyEventMouseMode call directly into our Driver Message servicing call inside Conhost.exe
// PrivateEnableAnyEventMouseMode is an internal-only "API" call that the vt commands can execute,
// but it is not represented as a function call on out public API surface.
// Arguments:
// - enabled - set to true to enable any-event mouse mode, false to disable
// Return Value:
// - true if successful (see DoSrvPrivateEnableAnyEventMouseMode). false otherwise.
bool ConhostInternalGetSet::PrivateEnableAnyEventMouseMode(const bool enabled)
{
DoSrvPrivateEnableAnyEventMouseMode(enabled);
return true;
}
// Routine Description:
// - Connects the PrivateEnableAlternateScroll call directly into our Driver Message servicing call inside Conhost.exe
// PrivateEnableAlternateScroll is an internal-only "API" call that the vt commands can execute,
// but it is not represented as a function call on out public API surface.
// Arguments:
// - enabled - set to true to enable alternate scroll mode, false to disable
// Return Value:
// - true if successful (see DoSrvPrivateEnableAnyEventMouseMode). false otherwise.
bool ConhostInternalGetSet::PrivateEnableAlternateScroll(const bool enabled)
{
DoSrvPrivateEnableAlternateScroll(enabled);
return true;
}
// Routine Description:
// - Connects the PrivateEraseAll call directly into our Driver Message servicing call inside Conhost.exe
// PrivateEraseAll is an internal-only "API" call that the vt commands can execute,

View file

@ -72,9 +72,7 @@ public:
bool SetConsoleWindowInfo(bool const absolute,
const SMALL_RECT& window) override;
bool PrivateSetCursorKeysMode(const bool applicationMode) override;
bool PrivateSetKeypadMode(const bool applicationMode) override;
bool PrivateEnableWin32InputMode(const bool win32InputMode) override;
bool SetInputMode(const Microsoft::Console::VirtualTerminal::TerminalInput::Mode mode, const bool enabled) override;
bool PrivateSetAnsiMode(const bool ansiMode) override;
bool PrivateSetScreenMode(const bool reverseMode) override;
@ -97,12 +95,6 @@ public:
bool PrivateUseMainScreenBuffer() override;
bool PrivateEnableVT200MouseMode(const bool enabled) override;
bool PrivateEnableUTF8ExtendedMouseMode(const bool enabled) override;
bool PrivateEnableSGRExtendedMouseMode(const bool enabled) override;
bool PrivateEnableButtonEventMouseMode(const bool enabled) override;
bool PrivateEnableAnyEventMouseMode(const bool enabled) override;
bool PrivateEnableAlternateScroll(const bool enabled) override;
bool PrivateEraseAll() override;
bool PrivateClearBuffer() override;

View file

@ -11,7 +11,7 @@
<Import Project="$(SolutionDir)src\common.build.pre.props" />
<ItemGroup>
<Midl Include="IConsoleHandoff.idl">
<!--
<!--
In Razzle, IDL files generate %FileName%.h
In Visual Studio, IDL files generate %FileName%_h.h
Visual Studio is easier to override than Razzle.
@ -31,6 +31,7 @@
<ItemGroup>
<ClInclude Include="$(IntDir)\IConsoleHandoff.h" />
<ClInclude Include="$(IntDir)\ITerminalHandoff.h" />
<ClInclude Include="nodefaultlib_shim.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="$(IntDir)\dlldata.c" />
@ -56,16 +57,27 @@
</ItemDefinitionGroup>
<ItemDefinitionGroup>
<ClCompile>
<AdditionalIncludeDirectories>..;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<CallingConvention>StdCall</CallingConvention>
<AdditionalIncludeDirectories>.;..;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<CallingConvention Condition="'$(Platform)'!='ARM64'">StdCall</CallingConvention>
<!-- Must be Stdcall on all platforms to resolve _ObjectStublessClient3 -->
<PreprocessorDefinitions>REGISTER_PROXY_DLL;WIN32;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PrecompiledHeader>NotUsing</PrecompiledHeader>
<BasicRuntimeChecks>Default</BasicRuntimeChecks>
<BufferSecurityCheck>false</BufferSecurityCheck>
<SDLCheck>false</SDLCheck>
<ForcedIncludeFiles>nodefaultlib_shim.h;%(ForcedIncludeFiles)</ForcedIncludeFiles>
</ClCompile>
<Link>
<ModuleDefinitionFile>OpenConsoleProxy.def</ModuleDefinitionFile>
<!--
Not depending on the CRT cuts binary size by half and prevents issues if this DLL
is copied elsewhere and executed outside of our app package without our bundled CRT present.
-->
<AdditionalDependencies />
<IgnoreAllDefaultLibraries>true</IgnoreAllDefaultLibraries>
<EntryPointSymbol>DllMain</EntryPointSymbol>
</Link>
</ItemDefinitionGroup>
<!-- Careful reordering these. Some default props (contained in these files) are order sensitive. -->
<Import Project="$(SolutionDir)src\common.build.post.props" />
</Project>
</Project>

View file

@ -41,6 +41,9 @@
<ClInclude Include="$(IntDir)\ITerminalHandoff.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="nodefaultlib_shim.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<Midl Include="IConsoleHandoff.idl">

View file

@ -0,0 +1,8 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#pragma once
#include <guiddef.h>
#define memcmp(a, b, c) (!InlineIsEqualGUID(a, b))

View file

@ -255,6 +255,14 @@ void Selection::ExtendSelection(_In_ COORD coordBufferPos)
srNewSelection.Top = _coordSelectionAnchor.Y;
}
// This function is called on WM_MOUSEMOVE.
// Prevent triggering an invalidation just because the mouse moved
// in the same cell without changing the actual (visible) selection.
if (_srSelectionRect == srNewSelection)
{
return;
}
// call special update method to modify the displayed selection in-place
// NOTE: Using HideSelection, editing the rectangle, then ShowSelection will cause flicker.
//_PaintUpdateSelection(&srNewSelection);

View file

@ -1167,15 +1167,7 @@ bool AdaptDispatch::ResetMode(const DispatchTypes::ModeParams param)
// - True if handled successfully. False otherwise.
bool AdaptDispatch::SetKeypadMode(const bool fApplicationMode)
{
bool success = true;
success = _pConApi->PrivateSetKeypadMode(fApplicationMode);
if (_ShouldPassThroughInputModeChange())
{
return false;
}
return success;
return _pConApi->SetInputMode(TerminalInput::Mode::Keypad, fApplicationMode);
}
// Method Description:
@ -1187,15 +1179,7 @@ bool AdaptDispatch::SetKeypadMode(const bool fApplicationMode)
// - True if handled successfully. False otherwise.
bool AdaptDispatch::EnableWin32InputMode(const bool win32InputMode)
{
bool success = true;
success = _pConApi->PrivateEnableWin32InputMode(win32InputMode);
if (_ShouldPassThroughInputModeChange())
{
return false;
}
return success;
return _pConApi->SetInputMode(TerminalInput::Mode::Win32, win32InputMode);
}
// - DECCKM - Sets the cursor keys input mode to either Application mode or Normal mode (true, false respectively)
@ -1205,15 +1189,7 @@ bool AdaptDispatch::EnableWin32InputMode(const bool win32InputMode)
// - True if handled successfully. False otherwise.
bool AdaptDispatch::SetCursorKeysMode(const bool applicationMode)
{
bool success = true;
success = _pConApi->PrivateSetCursorKeysMode(applicationMode);
if (_ShouldPassThroughInputModeChange())
{
return false;
}
return success;
return _pConApi->SetInputMode(TerminalInput::Mode::CursorKey, applicationMode);
}
// - att610 - Enables or disables the cursor blinking.
@ -2096,15 +2072,7 @@ bool AdaptDispatch::EnableDECCOLMSupport(const bool enabled) noexcept
// True if handled successfully. False otherwise.
bool AdaptDispatch::EnableVT200MouseMode(const bool enabled)
{
bool success = true;
success = _pConApi->PrivateEnableVT200MouseMode(enabled);
if (_ShouldPassThroughInputModeChange())
{
return false;
}
return success;
return _pConApi->SetInputMode(TerminalInput::Mode::DefaultMouseTracking, enabled);
}
//Routine Description:
@ -2116,15 +2084,7 @@ bool AdaptDispatch::EnableVT200MouseMode(const bool enabled)
// True if handled successfully. False otherwise.
bool AdaptDispatch::EnableUTF8ExtendedMouseMode(const bool enabled)
{
bool success = true;
success = _pConApi->PrivateEnableUTF8ExtendedMouseMode(enabled);
if (_ShouldPassThroughInputModeChange())
{
return false;
}
return success;
return _pConApi->SetInputMode(TerminalInput::Mode::Utf8MouseEncoding, enabled);
}
//Routine Description:
@ -2136,15 +2096,7 @@ bool AdaptDispatch::EnableUTF8ExtendedMouseMode(const bool enabled)
// True if handled successfully. False otherwise.
bool AdaptDispatch::EnableSGRExtendedMouseMode(const bool enabled)
{
bool success = true;
success = _pConApi->PrivateEnableSGRExtendedMouseMode(enabled);
if (_ShouldPassThroughInputModeChange())
{
return false;
}
return success;
return _pConApi->SetInputMode(TerminalInput::Mode::SgrMouseEncoding, enabled);
}
//Routine Description:
@ -2155,15 +2107,7 @@ bool AdaptDispatch::EnableSGRExtendedMouseMode(const bool enabled)
// True if handled successfully. False otherwise.
bool AdaptDispatch::EnableButtonEventMouseMode(const bool enabled)
{
bool success = true;
success = _pConApi->PrivateEnableButtonEventMouseMode(enabled);
if (_ShouldPassThroughInputModeChange())
{
return false;
}
return success;
return _pConApi->SetInputMode(TerminalInput::Mode::ButtonEventMouseTracking, enabled);
}
//Routine Description:
@ -2175,15 +2119,7 @@ bool AdaptDispatch::EnableButtonEventMouseMode(const bool enabled)
// True if handled successfully. False otherwise.
bool AdaptDispatch::EnableAnyEventMouseMode(const bool enabled)
{
bool success = true;
success = _pConApi->PrivateEnableAnyEventMouseMode(enabled);
if (_ShouldPassThroughInputModeChange())
{
return false;
}
return success;
return _pConApi->SetInputMode(TerminalInput::Mode::AnyEventMouseTracking, enabled);
}
//Routine Description:
@ -2195,15 +2131,7 @@ bool AdaptDispatch::EnableAnyEventMouseMode(const bool enabled)
// True if handled successfully. False otherwise.
bool AdaptDispatch::EnableAlternateScroll(const bool enabled)
{
bool success = true;
success = _pConApi->PrivateEnableAlternateScroll(enabled);
if (_ShouldPassThroughInputModeChange())
{
return false;
}
return success;
return _pConApi->SetInputMode(TerminalInput::Mode::AlternateScroll, enabled);
}
//Routine Description:
@ -2672,22 +2600,3 @@ void AdaptDispatch::_ReportDECSTBMSetting() const
response += L"r\033\\";
_WriteResponse(response);
}
// Routine Description:
// - Determines whether we should pass any sequence that manipulates
// TerminalInput's input generator through the PTY. It encapsulates
// a check for whether the PTY is in use.
// Return value:
// True if the request should be passed.
bool AdaptDispatch::_ShouldPassThroughInputModeChange() const
{
// If we're a conpty, AND WE'RE IN VT INPUT MODE, always pass input mode requests
// The VT Input mode check is to work around ssh.exe v7.7, which uses VT
// output, but not Input.
// The original comment said, "Once the conpty supports these types of input,
// this check can be removed. See GH#4911". Unfortunately, time has shown
// us that SSH 7.7 _also_ requests mouse input and that can have a user interface
// impact on the actual connected terminal. We can't remove this check,
// because SSH <=7.7 is out in the wild on all versions of Windows <=2004.
return _pConApi->IsConsolePty() && _pConApi->PrivateIsVtInputEnabled();
}

View file

@ -194,8 +194,6 @@ namespace Microsoft::Console::VirtualTerminal
void _ReportSGRSetting() const;
void _ReportDECSTBMSetting() const;
bool _ShouldPassThroughInputModeChange() const;
std::vector<bool> _tabStopColumns;
bool _initDefaultTabStops = true;

View file

@ -15,6 +15,7 @@ Author(s):
#pragma once
#include "../input/terminalInput.hpp"
#include "../../types/inc/IInputEvent.hpp"
#include "../../buffer/out/LineRendition.hpp"
#include "../../buffer/out/TextAttribute.hpp"
@ -46,9 +47,8 @@ namespace Microsoft::Console::VirtualTerminal
size_t& eventsWritten) = 0;
virtual bool SetConsoleWindowInfo(const bool absolute,
const SMALL_RECT& window) = 0;
virtual bool PrivateSetCursorKeysMode(const bool applicationMode) = 0;
virtual bool PrivateSetKeypadMode(const bool applicationMode) = 0;
virtual bool PrivateEnableWin32InputMode(const bool win32InputMode) = 0;
virtual bool SetInputMode(const TerminalInput::Mode mode, const bool enabled) = 0;
virtual bool PrivateSetAnsiMode(const bool ansiMode) = 0;
virtual bool PrivateSetScreenMode(const bool reverseMode) = 0;
@ -66,12 +66,6 @@ namespace Microsoft::Console::VirtualTerminal
virtual bool PrivateUseAlternateScreenBuffer() = 0;
virtual bool PrivateUseMainScreenBuffer() = 0;
virtual bool PrivateEnableVT200MouseMode(const bool enabled) = 0;
virtual bool PrivateEnableUTF8ExtendedMouseMode(const bool enabled) = 0;
virtual bool PrivateEnableSGRExtendedMouseMode(const bool enabled) = 0;
virtual bool PrivateEnableButtonEventMouseMode(const bool enabled) = 0;
virtual bool PrivateEnableAnyEventMouseMode(const bool enabled) = 0;
virtual bool PrivateEnableAlternateScroll(const bool enabled) = 0;
virtual bool PrivateEraseAll() = 0;
virtual bool PrivateClearBuffer() = 0;
virtual bool GetUserDefaultCursorStyle(CursorType& style) = 0;

View file

@ -298,7 +298,7 @@ public:
s_pwszInputExpected = L"\x0";
VERIFY_ARE_EQUAL(fExpectedKeyHandled, mouseInput->HandleMouse({ 0, 0 }, uiButton, sModifierKeystate, sScrollDelta, {}));
mouseInput->EnableDefaultTracking(true);
mouseInput->SetInputMode(TerminalInput::Mode::DefaultMouseTracking, true);
for (int i = 0; i < s_iTestCoordsLength; i++)
{
@ -318,7 +318,7 @@ public:
NoThrowString().Format(L"(x,y)=(%d,%d)", Coord.X, Coord.Y));
}
mouseInput->EnableButtonEventTracking(true);
mouseInput->SetInputMode(TerminalInput::Mode::ButtonEventMouseTracking, true);
for (int i = 0; i < s_iTestCoordsLength; i++)
{
COORD Coord = s_rgTestCoords[i];
@ -337,7 +337,7 @@ public:
NoThrowString().Format(L"(x,y)=(%d,%d)", Coord.X, Coord.Y));
}
mouseInput->EnableAnyEventTracking(true);
mouseInput->SetInputMode(TerminalInput::Mode::AnyEventMouseTracking, true);
for (int i = 0; i < s_iTestCoordsLength; i++)
{
COORD Coord = s_rgTestCoords[i];
@ -381,11 +381,11 @@ public:
s_pwszInputExpected = L"\x0";
VERIFY_ARE_EQUAL(fExpectedKeyHandled, mouseInput->HandleMouse({ 0, 0 }, uiButton, sModifierKeystate, sScrollDelta, {}));
mouseInput->SetUtf8ExtendedMode(true);
mouseInput->SetInputMode(TerminalInput::Mode::Utf8MouseEncoding, true);
short MaxCoord = SHORT_MAX - 33;
mouseInput->EnableDefaultTracking(true);
mouseInput->SetInputMode(TerminalInput::Mode::DefaultMouseTracking, true);
for (int i = 0; i < s_iTestCoordsLength; i++)
{
COORD Coord = s_rgTestCoords[i];
@ -404,7 +404,7 @@ public:
NoThrowString().Format(L"(x,y)=(%d,%d)", Coord.X, Coord.Y));
}
mouseInput->EnableButtonEventTracking(true);
mouseInput->SetInputMode(TerminalInput::Mode::ButtonEventMouseTracking, true);
for (int i = 0; i < s_iTestCoordsLength; i++)
{
COORD Coord = s_rgTestCoords[i];
@ -423,7 +423,7 @@ public:
NoThrowString().Format(L"(x,y)=(%d,%d)", Coord.X, Coord.Y));
}
mouseInput->EnableAnyEventTracking(true);
mouseInput->SetInputMode(TerminalInput::Mode::AnyEventMouseTracking, true);
for (int i = 0; i < s_iTestCoordsLength; i++)
{
COORD Coord = s_rgTestCoords[i];
@ -467,13 +467,13 @@ public:
s_pwszInputExpected = L"\x0";
VERIFY_ARE_EQUAL(fExpectedKeyHandled, mouseInput->HandleMouse({ 0, 0 }, uiButton, sModifierKeystate, sScrollDelta, {}));
mouseInput->SetSGRExtendedMode(true);
mouseInput->SetInputMode(TerminalInput::Mode::SgrMouseEncoding, true);
// SGR Mode should be able to handle any arbitrary coords.
// However, mouse moves are only handled in Any Event mode
fExpectedKeyHandled = uiButton != WM_MOUSEMOVE;
mouseInput->EnableDefaultTracking(true);
mouseInput->SetInputMode(TerminalInput::Mode::DefaultMouseTracking, true);
for (int i = 0; i < s_iTestCoordsLength; i++)
{
COORD Coord = s_rgTestCoords[i];
@ -487,7 +487,7 @@ public:
NoThrowString().Format(L"(x,y)=(%d,%d)", Coord.X, Coord.Y));
}
mouseInput->EnableButtonEventTracking(true);
mouseInput->SetInputMode(TerminalInput::Mode::ButtonEventMouseTracking, true);
for (int i = 0; i < s_iTestCoordsLength; i++)
{
COORD Coord = s_rgTestCoords[i];
@ -506,7 +506,7 @@ public:
}
fExpectedKeyHandled = true;
mouseInput->EnableAnyEventTracking(true);
mouseInput->SetInputMode(TerminalInput::Mode::AnyEventMouseTracking, true);
for (int i = 0; i < s_iTestCoordsLength; i++)
{
COORD Coord = s_rgTestCoords[i];
@ -550,7 +550,7 @@ public:
VERIFY_ARE_EQUAL(fExpectedKeyHandled, mouseInput->HandleMouse({ 0, 0 }, uiButton, sModifierKeystate, sScrollDelta, {}));
// Default Tracking, Default Encoding
mouseInput->EnableDefaultTracking(true);
mouseInput->SetInputMode(TerminalInput::Mode::DefaultMouseTracking, true);
for (int i = 0; i < s_iTestCoordsLength; i++)
{
@ -571,7 +571,7 @@ public:
}
// Default Tracking, UTF8 Encoding
mouseInput->SetUtf8ExtendedMode(true);
mouseInput->SetInputMode(TerminalInput::Mode::Utf8MouseEncoding, true);
short MaxCoord = SHORT_MAX - 33;
for (int i = 0; i < s_iTestCoordsLength; i++)
{
@ -592,7 +592,7 @@ public:
}
// Default Tracking, SGR Encoding
mouseInput->SetSGRExtendedMode(true);
mouseInput->SetInputMode(TerminalInput::Mode::SgrMouseEncoding, true);
fExpectedKeyHandled = true; // SGR Mode should be able to handle any arbitrary coords.
for (int i = 0; i < s_iTestCoordsLength; i++)
{
@ -620,7 +620,7 @@ public:
Log::Comment(L"Enable alternate scroll mode in the alt screen buffer");
mouseInput->UseAlternateScreenBuffer();
mouseInput->EnableAlternateScroll(true);
mouseInput->SetInputMode(TerminalInput::Mode::AlternateScroll, true);
Log::Comment(L"Test mouse wheel scrolling up");
s_pwszInputExpected = L"\x1B[A";
@ -631,7 +631,7 @@ public:
VERIFY_IS_TRUE(mouseInput->HandleMouse({ 0, 0 }, WM_MOUSEWHEEL, noModifierKeys, -WHEEL_DELTA, {}));
Log::Comment(L"Enable cursor keys mode");
mouseInput->ChangeCursorKeysMode(true);
mouseInput->SetInputMode(TerminalInput::Mode::CursorKey, true);
Log::Comment(L"Test mouse wheel scrolling up");
s_pwszInputExpected = L"\x1BOA";
@ -643,12 +643,12 @@ public:
Log::Comment(L"Confirm no effect when scroll mode is disabled");
mouseInput->UseAlternateScreenBuffer();
mouseInput->EnableAlternateScroll(false);
mouseInput->SetInputMode(TerminalInput::Mode::AlternateScroll, false);
VERIFY_IS_FALSE(mouseInput->HandleMouse({ 0, 0 }, WM_MOUSEWHEEL, noModifierKeys, WHEEL_DELTA, {}));
Log::Comment(L"Confirm no effect when using the main buffer");
mouseInput->UseMainScreenBuffer();
mouseInput->EnableAlternateScroll(true);
mouseInput->SetInputMode(TerminalInput::Mode::AlternateScroll, true);
VERIFY_IS_FALSE(mouseInput->HandleMouse({ 0, 0 }, WM_MOUSEWHEEL, noModifierKeys, WHEEL_DELTA, {}));
}
};

View file

@ -112,35 +112,17 @@ public:
return _setConsoleWindowInfoResult;
}
bool PrivateSetCursorKeysMode(const bool applicationMode) override
bool SetInputMode(const TerminalInput::Mode mode, const bool enabled) override
{
Log::Comment(L"PrivateSetCursorKeysMode MOCK called...");
Log::Comment(L"SetInputMode MOCK called...");
if (_privateSetCursorKeysModeResult)
if (_setInputModeResult)
{
VERIFY_ARE_EQUAL(_cursorKeysApplicationMode, applicationMode);
VERIFY_ARE_EQUAL(_expectedInputMode, mode);
VERIFY_ARE_EQUAL(_expectedInputModeEnabled, enabled);
}
return _privateSetCursorKeysModeResult;
}
bool PrivateSetKeypadMode(const bool applicationMode) override
{
Log::Comment(L"PrivateSetKeypadMode MOCK called...");
if (_privateSetKeypadModeResult)
{
VERIFY_ARE_EQUAL(_keypadApplicationMode, applicationMode);
}
return _privateSetKeypadModeResult;
}
bool PrivateEnableWin32InputMode(const bool /*win32InputMode*/) override
{
Log::Comment(L"PrivateEnableWin32InputMode MOCK called...");
return true;
return _setInputModeResult;
}
bool PrivateSetAnsiMode(const bool ansiMode) override
@ -352,66 +334,6 @@ public:
return true;
}
bool PrivateEnableVT200MouseMode(const bool enabled) override
{
Log::Comment(L"PrivateEnableVT200MouseMode MOCK called...");
if (_privateEnableVT200MouseModeResult)
{
VERIFY_ARE_EQUAL(_expectedMouseEnabled, enabled);
}
return _privateEnableVT200MouseModeResult;
}
bool PrivateEnableUTF8ExtendedMouseMode(const bool enabled) override
{
Log::Comment(L"PrivateEnableUTF8ExtendedMouseMode MOCK called...");
if (_privateEnableUTF8ExtendedMouseModeResult)
{
VERIFY_ARE_EQUAL(_expectedMouseEnabled, enabled);
}
return _privateEnableUTF8ExtendedMouseModeResult;
}
bool PrivateEnableSGRExtendedMouseMode(const bool enabled) override
{
Log::Comment(L"PrivateEnableSGRExtendedMouseMode MOCK called...");
if (_privateEnableSGRExtendedMouseModeResult)
{
VERIFY_ARE_EQUAL(_expectedMouseEnabled, enabled);
}
return _privateEnableSGRExtendedMouseModeResult;
}
bool PrivateEnableButtonEventMouseMode(const bool enabled) override
{
Log::Comment(L"PrivateEnableButtonEventMouseMode MOCK called...");
if (_privateEnableButtonEventMouseModeResult)
{
VERIFY_ARE_EQUAL(_expectedMouseEnabled, enabled);
}
return _privateEnableButtonEventMouseModeResult;
}
bool PrivateEnableAnyEventMouseMode(const bool enabled) override
{
Log::Comment(L"PrivateEnableAnyEventMouseMode MOCK called...");
if (_privateEnableAnyEventMouseModeResult)
{
VERIFY_ARE_EQUAL(_expectedMouseEnabled, enabled);
}
return _privateEnableAnyEventMouseModeResult;
}
bool PrivateEnableAlternateScroll(const bool enabled) override
{
Log::Comment(L"PrivateEnableAlternateScroll MOCK called...");
if (_privateEnableAlternateScrollResult)
{
VERIFY_ARE_EQUAL(_expectedAlternateScrollEnabled, enabled);
}
return _privateEnableAlternateScrollResult;
}
bool PrivateEraseAll() override
{
Log::Comment(L"PrivateEraseAll MOCK called...");
@ -787,10 +709,9 @@ public:
COORD _expectedScreenBufferSize = { 0, 0 };
SMALL_RECT _expectedScreenBufferViewport{ 0, 0, 0, 0 };
bool _privateSetCursorKeysModeResult = false;
bool _privateSetKeypadModeResult = false;
bool _cursorKeysApplicationMode = false;
bool _keypadApplicationMode = false;
bool _setInputModeResult = false;
TerminalInput::Mode _expectedInputMode;
bool _expectedInputModeEnabled = false;
bool _privateSetAnsiModeResult = false;
bool _expectedAnsiMode = false;
bool _privateAllowCursorBlinkingResult = false;
@ -803,14 +724,6 @@ public:
bool _setConsoleTitleWResult = false;
std::wstring_view _expectedWindowTitle{};
bool _expectedMouseEnabled = false;
bool _expectedAlternateScrollEnabled = false;
bool _privateEnableVT200MouseModeResult = false;
bool _privateEnableUTF8ExtendedMouseModeResult = false;
bool _privateEnableSGRExtendedMouseModeResult = false;
bool _privateEnableButtonEventMouseModeResult = false;
bool _privateEnableAnyEventMouseModeResult = false;
bool _privateEnableAlternateScrollResult = false;
bool _setCursorStyleResult = false;
CursorType _expectedCursorStyle;
bool _setCursorColorResult = false;
@ -2100,15 +2013,17 @@ public:
// success cases
// set numeric mode = true
Log::Comment(L"Test 1: application mode = false");
_testGetSet->_privateSetCursorKeysModeResult = TRUE;
_testGetSet->_cursorKeysApplicationMode = false;
_testGetSet->_setInputModeResult = true;
_testGetSet->_expectedInputMode = TerminalInput::Mode::CursorKey;
_testGetSet->_expectedInputModeEnabled = false;
VERIFY_IS_TRUE(_pDispatch.get()->SetCursorKeysMode(false));
// set numeric mode = false
Log::Comment(L"Test 2: application mode = true");
_testGetSet->_privateSetCursorKeysModeResult = TRUE;
_testGetSet->_cursorKeysApplicationMode = true;
_testGetSet->_setInputModeResult = true;
_testGetSet->_expectedInputMode = TerminalInput::Mode::CursorKey;
_testGetSet->_expectedInputModeEnabled = true;
VERIFY_IS_TRUE(_pDispatch.get()->SetCursorKeysMode(true));
}
@ -2120,15 +2035,17 @@ public:
// success cases
// set numeric mode = true
Log::Comment(L"Test 1: application mode = false");
_testGetSet->_privateSetKeypadModeResult = TRUE;
_testGetSet->_keypadApplicationMode = false;
_testGetSet->_setInputModeResult = true;
_testGetSet->_expectedInputMode = TerminalInput::Mode::Keypad;
_testGetSet->_expectedInputModeEnabled = false;
VERIFY_IS_TRUE(_pDispatch.get()->SetKeypadMode(false));
// set numeric mode = false
Log::Comment(L"Test 2: application mode = true");
_testGetSet->_privateSetKeypadModeResult = TRUE;
_testGetSet->_keypadApplicationMode = true;
_testGetSet->_setInputModeResult = true;
_testGetSet->_expectedInputMode = TerminalInput::Mode::Keypad;
_testGetSet->_expectedInputModeEnabled = true;
VERIFY_IS_TRUE(_pDispatch.get()->SetKeypadMode(true));
}
@ -2316,45 +2233,51 @@ public:
Log::Comment(L"Starting test...");
Log::Comment(L"Test 1: Test Default Mouse Mode");
_testGetSet->_expectedMouseEnabled = true;
_testGetSet->_privateEnableVT200MouseModeResult = TRUE;
_testGetSet->_expectedInputModeEnabled = true;
_testGetSet->_expectedInputMode = TerminalInput::Mode::DefaultMouseTracking;
_testGetSet->_setInputModeResult = true;
VERIFY_IS_TRUE(_pDispatch.get()->EnableVT200MouseMode(true));
_testGetSet->_expectedMouseEnabled = false;
_testGetSet->_expectedInputModeEnabled = false;
VERIFY_IS_TRUE(_pDispatch.get()->EnableVT200MouseMode(false));
Log::Comment(L"Test 2: Test UTF-8 Extended Mouse Mode");
_testGetSet->_expectedMouseEnabled = true;
_testGetSet->_privateEnableUTF8ExtendedMouseModeResult = TRUE;
_testGetSet->_expectedInputModeEnabled = true;
_testGetSet->_expectedInputMode = TerminalInput::Mode::Utf8MouseEncoding;
_testGetSet->_setInputModeResult = true;
VERIFY_IS_TRUE(_pDispatch.get()->EnableUTF8ExtendedMouseMode(true));
_testGetSet->_expectedMouseEnabled = false;
_testGetSet->_expectedInputModeEnabled = false;
VERIFY_IS_TRUE(_pDispatch.get()->EnableUTF8ExtendedMouseMode(false));
Log::Comment(L"Test 3: Test SGR Extended Mouse Mode");
_testGetSet->_expectedMouseEnabled = true;
_testGetSet->_privateEnableSGRExtendedMouseModeResult = TRUE;
_testGetSet->_expectedInputModeEnabled = true;
_testGetSet->_expectedInputMode = TerminalInput::Mode::SgrMouseEncoding;
_testGetSet->_setInputModeResult = true;
VERIFY_IS_TRUE(_pDispatch.get()->EnableSGRExtendedMouseMode(true));
_testGetSet->_expectedMouseEnabled = false;
_testGetSet->_expectedInputModeEnabled = false;
VERIFY_IS_TRUE(_pDispatch.get()->EnableSGRExtendedMouseMode(false));
Log::Comment(L"Test 4: Test Button-Event Mouse Mode");
_testGetSet->_expectedMouseEnabled = true;
_testGetSet->_privateEnableButtonEventMouseModeResult = TRUE;
_testGetSet->_expectedInputModeEnabled = true;
_testGetSet->_expectedInputMode = TerminalInput::Mode::ButtonEventMouseTracking;
_testGetSet->_setInputModeResult = true;
VERIFY_IS_TRUE(_pDispatch.get()->EnableButtonEventMouseMode(true));
_testGetSet->_expectedMouseEnabled = false;
_testGetSet->_expectedInputModeEnabled = false;
VERIFY_IS_TRUE(_pDispatch.get()->EnableButtonEventMouseMode(false));
Log::Comment(L"Test 5: Test Any-Event Mouse Mode");
_testGetSet->_expectedMouseEnabled = true;
_testGetSet->_privateEnableAnyEventMouseModeResult = TRUE;
_testGetSet->_expectedInputModeEnabled = true;
_testGetSet->_expectedInputMode = TerminalInput::Mode::AnyEventMouseTracking;
_testGetSet->_setInputModeResult = true;
VERIFY_IS_TRUE(_pDispatch.get()->EnableAnyEventMouseMode(true));
_testGetSet->_expectedMouseEnabled = false;
_testGetSet->_expectedInputModeEnabled = false;
VERIFY_IS_TRUE(_pDispatch.get()->EnableAnyEventMouseMode(false));
Log::Comment(L"Test 6: Test Alt Scroll Mouse Mode");
_testGetSet->_expectedAlternateScrollEnabled = true;
_testGetSet->_privateEnableAlternateScrollResult = TRUE;
_testGetSet->_expectedInputModeEnabled = true;
_testGetSet->_expectedInputMode = TerminalInput::Mode::AlternateScroll;
_testGetSet->_setInputModeResult = true;
VERIFY_IS_TRUE(_pDispatch.get()->EnableAlternateScroll(true));
_testGetSet->_expectedAlternateScrollEnabled = false;
_testGetSet->_expectedInputModeEnabled = false;
VERIFY_IS_TRUE(_pDispatch.get()->EnableAlternateScroll(false));
}

View file

@ -285,13 +285,13 @@ static constexpr short _encodeDefaultCoordinate(const short sCoordinateValue) no
// - true, if we are tracking mouse input. False, otherwise
bool TerminalInput::IsTrackingMouseInput() const noexcept
{
return (_mouseInputState.trackingMode != TrackingMode::None);
return _inputMode.any(Mode::DefaultMouseTracking, Mode::ButtonEventMouseTracking, Mode::AnyEventMouseTracking);
}
// Routine Description:
// - Attempt to handle the given mouse coordinates and windows button as a VT-style mouse event.
// If the event should be transmitted in the selected mouse mode, then we'll try and
// encode the event according to the rules of the selected ExtendedMode, and insert those characters into the input buffer.
// encode the event according to the rules of the encoding mode, and insert those characters into the input buffer.
// Parameters:
// - position - The windows coordinates (top,left = 0,0) of the mouse event
// - button - the message to decode.
@ -339,7 +339,7 @@ bool TerminalInput::HandleMouse(const COORD position,
}
else
{
success = (_mouseInputState.trackingMode != TrackingMode::None);
success = IsTrackingMouseInput();
if (success)
{
// isHover is only true for WM_MOUSEMOVE events
@ -363,30 +363,23 @@ bool TerminalInput::HandleMouse(const COORD position,
// In AnyEvent, all coord change hovers are sent
const bool physicalButtonPressed = realButton != WM_LBUTTONUP;
success = (isButton && _mouseInputState.trackingMode != TrackingMode::None) ||
(isHover && _mouseInputState.trackingMode == TrackingMode::ButtonEvent && ((!sameCoord) && (physicalButtonPressed))) ||
(isHover && _mouseInputState.trackingMode == TrackingMode::AnyEvent && !sameCoord);
success = (isButton && IsTrackingMouseInput()) ||
(isHover && _inputMode.test(Mode::ButtonEventMouseTracking) && ((!sameCoord) && (physicalButtonPressed))) ||
(isHover && _inputMode.test(Mode::AnyEventMouseTracking) && !sameCoord);
if (success)
{
std::wstring sequence;
switch (_mouseInputState.extendedMode)
if (_inputMode.test(Mode::Utf8MouseEncoding))
{
case ExtendedMode::None:
sequence = _GenerateDefaultSequence(position,
realButton,
isHover,
modifierKeyState,
delta);
break;
case ExtendedMode::Utf8:
sequence = _GenerateUtf8Sequence(position,
realButton,
isHover,
modifierKeyState,
delta);
break;
case ExtendedMode::Sgr:
}
else if (_inputMode.test(Mode::SgrMouseEncoding))
{
// For SGR encoding, if no physical buttons were pressed,
// then we want to handle hovers with WM_MOUSEMOVE.
// However, if we're dragging (WM_MOUSEMOVE with a button pressed),
@ -397,13 +390,15 @@ bool TerminalInput::HandleMouse(const COORD position,
isHover,
modifierKeyState,
delta);
break;
case ExtendedMode::Urxvt:
default:
success = false;
break;
}
else
{
sequence = _GenerateDefaultSequence(position,
realButton,
isHover,
modifierKeyState,
delta);
}
success = !sequence.empty();
if (success)
@ -411,7 +406,7 @@ bool TerminalInput::HandleMouse(const COORD position,
_SendInputSequence(sequence);
success = true;
}
if (_mouseInputState.trackingMode == TrackingMode::ButtonEvent || _mouseInputState.trackingMode == TrackingMode::AnyEvent)
if (_inputMode.any(Mode::ButtonEventMouseTracking, Mode::AnyEventMouseTracking))
{
_mouseInputState.lastPos.X = position.X;
_mouseInputState.lastPos.Y = position.Y;
@ -547,10 +542,10 @@ std::wstring TerminalInput::_GenerateSGRSequence(const COORD position,
// - delta: The scroll wheel delta of the input event
// Return value:
// True iff the alternate buffer is active and alternate scroll mode is enabled and the event is a mouse wheel event.
bool TerminalInput::_ShouldSendAlternateScroll(const unsigned int button, const short delta) const noexcept
bool TerminalInput::_ShouldSendAlternateScroll(const unsigned int button, const short delta) const
{
return _mouseInputState.inAlternateBuffer &&
_mouseInputState.alternateScroll &&
_inputMode.test(Mode::AlternateScroll) &&
(button == WM_MOUSEWHEEL || button == WM_MOUSEHWHEEL) && delta != 0;
}
@ -560,15 +555,15 @@ bool TerminalInput::_ShouldSendAlternateScroll(const unsigned int button, const
// - delta: The scroll wheel delta of the input event
// Return value:
// True iff the input sequence was sent successfully.
bool TerminalInput::_SendAlternateScroll(const short delta) const noexcept
bool TerminalInput::_SendAlternateScroll(const short delta) const
{
if (delta > 0)
{
_SendInputSequence(_cursorApplicationMode ? ApplicationUpSequence : CursorUpSequence);
_SendInputSequence(_inputMode.test(Mode::CursorKey) ? ApplicationUpSequence : CursorUpSequence);
}
else
{
_SendInputSequence(_cursorApplicationMode ? ApplicationDownSequence : CursorDownSequence);
_SendInputSequence(_inputMode.test(Mode::CursorKey) ? ApplicationDownSequence : CursorDownSequence);
}
return true;
}

View file

@ -7,91 +7,6 @@
using namespace Microsoft::Console::VirtualTerminal;
// Routine Description:
// - Either enables or disables UTF-8 extended mode encoding. This *should* cause
// the coordinates of a mouse event to be encoded as a UTF-8 byte stream, however, because windows' input is
// typically UTF-16 encoded, it emits a UTF-16 stream.
// Does NOT enable or disable mouse mode by itself. This matches the behavior I found in Ubuntu terminals.
// Parameters:
// - enable - either enable or disable.
// Return value:
// <none>
void TerminalInput::SetUtf8ExtendedMode(const bool enable) noexcept
{
_mouseInputState.extendedMode = enable ? ExtendedMode::Utf8 : ExtendedMode::None;
}
// Routine Description:
// - Either enables or disables SGR extended mode encoding. This causes the
// coordinates of a mouse event to be emitted in a human readable format,
// eg, x,y=203,504 -> "^[[<B;203;504M". This way, applications don't need to worry about character encoding.
// Does NOT enable or disable mouse mode by itself. This matches the behavior I found in Ubuntu terminals.
// Parameters:
// - enable - either enable or disable.
// Return value:
// <none>
void TerminalInput::SetSGRExtendedMode(const bool enable) noexcept
{
_mouseInputState.extendedMode = enable ? ExtendedMode::Sgr : ExtendedMode::None;
}
// Routine Description:
// - Either enables or disables mouse mode handling. Leaves the extended mode alone,
// so if we disable then re-enable mouse mode without toggling an extended mode, the mode will persist.
// Parameters:
// - enable - either enable or disable.
// Return value:
// <none>
void TerminalInput::EnableDefaultTracking(const bool enable) noexcept
{
_mouseInputState.trackingMode = enable ? TrackingMode::Default : TrackingMode::None;
_mouseInputState.lastPos = { -1, -1 }; // Clear out the last saved mouse position & button.
_mouseInputState.lastButton = 0;
}
// Routine Description:
// - Either enables or disables ButtonEvent mouse handling. Button Event mode
// sends additional sequences when a button is pressed and the mouse changes character cells.
// Leaves the extended mode alone, so if we disable then re-enable mouse mode
// without toggling an extended mode, the mode will persist.
// Parameters:
// - enable - either enable or disable.
// Return value:
// <none>
void TerminalInput::EnableButtonEventTracking(const bool enable) noexcept
{
_mouseInputState.trackingMode = enable ? TrackingMode::ButtonEvent : TrackingMode::None;
_mouseInputState.lastPos = { -1, -1 }; // Clear out the last saved mouse position & button.
_mouseInputState.lastButton = 0;
}
// Routine Description:
// - Either enables or disables AnyEvent mouse handling. Any Event mode sends sequences
// for any and every mouse event, regardless if a button is pressed or not.
// Leaves the extended mode alone, so if we disable then re-enable mouse mode
// without toggling an extended mode, the mode will persist.
// Parameters:
// - enable - either enable or disable.
// Return value:
// <none>
void TerminalInput::EnableAnyEventTracking(const bool enable) noexcept
{
_mouseInputState.trackingMode = enable ? TrackingMode::AnyEvent : TrackingMode::None;
_mouseInputState.lastPos = { -1, -1 }; // Clear out the last saved mouse position & button.
_mouseInputState.lastButton = 0;
}
// Routine Description:
// - Enables alternate scroll mode. This sends Cursor Up/down sequences when in the alternate buffer
// Parameters:
// - enable - either enable or disable.
// Return value:
// <none>
void TerminalInput::EnableAlternateScroll(const bool enable) noexcept
{
_mouseInputState.alternateScroll = enable;
}
// Routine Description:
// - Notify the MouseInput handler that the screen buffer has been swapped to the alternate buffer
// Parameters:

View file

@ -250,25 +250,32 @@ const wchar_t* const CTRL_QUESTIONMARK_SEQUENCE = L"\x7F";
const wchar_t* const CTRL_ALT_SLASH_SEQUENCE = L"\x1b\x1f";
const wchar_t* const CTRL_ALT_QUESTIONMARK_SEQUENCE = L"\x1b\x7F";
void TerminalInput::ChangeAnsiMode(const bool ansiMode) noexcept
void TerminalInput::SetInputMode(const Mode mode, const bool enabled)
{
_ansiMode = ansiMode;
// If we're changing a tracking mode, we always clear other tracking modes first.
// We also clear out the last saved mouse position & button.
if (mode == Mode::DefaultMouseTracking || mode == Mode::ButtonEventMouseTracking || mode == Mode::AnyEventMouseTracking)
{
_inputMode.reset_all(Mode::DefaultMouseTracking, Mode::ButtonEventMouseTracking, Mode::AnyEventMouseTracking);
_mouseInputState.lastPos = { -1, -1 };
_mouseInputState.lastButton = 0;
}
// But if we're changing the encoding, we only clear out the other encoding modes
// when enabling a new encoding - not when disabling.
if ((mode == Mode::Utf8MouseEncoding || mode == Mode::SgrMouseEncoding) && enabled)
{
_inputMode.reset_all(Mode::Utf8MouseEncoding, Mode::SgrMouseEncoding);
}
_inputMode.set(mode, enabled);
}
void TerminalInput::ChangeKeypadMode(const bool applicationMode) noexcept
bool TerminalInput::GetInputMode(const Mode mode) const
{
_keypadApplicationMode = applicationMode;
return _inputMode.test(mode);
}
void TerminalInput::ChangeCursorKeysMode(const bool applicationMode) noexcept
{
_cursorApplicationMode = applicationMode;
}
void TerminalInput::ChangeWin32InputMode(const bool win32InputMode) noexcept
{
_win32InputMode = win32InputMode;
}
void TerminalInput::ForceDisableWin32InputMode(const bool win32InputMode) noexcept
{
_forceDisableWin32InputMode = win32InputMode;
@ -530,7 +537,7 @@ bool TerminalInput::HandleKey(const IInputEvent* const pInEvent)
// GH#4999 - If we're in win32-input mode, skip straight to doing that.
// Since this mode handles all types of key events, do nothing else.
// Only do this if win32-input-mode support isn't manually disabled.
if (_win32InputMode && !_forceDisableWin32InputMode)
if (_inputMode.test(Mode::Win32) && !_forceDisableWin32InputMode)
{
const auto seq = _GenerateWin32KeySequence(keyEvent);
_SendInputSequence(seq);
@ -655,7 +662,7 @@ bool TerminalInput::HandleKey(const IInputEvent* const pInEvent)
// Check any other key mappings (like those for the F1-F12 keys).
// These mappings will kick in no matter which modifiers are pressed and as such
// must be checked last, or otherwise we'd override more complex key combinations.
const auto mapping = _getKeyMapping(keyEvent, _ansiMode, _cursorApplicationMode, _keypadApplicationMode);
const auto mapping = _getKeyMapping(keyEvent, _inputMode.test(Mode::Ansi), _inputMode.test(Mode::CursorKey), _inputMode.test(Mode::Keypad));
if (_translateDefaultMapping(keyEvent, mapping, senderFunc))
{
return true;

View file

@ -34,11 +34,26 @@ namespace Microsoft::Console::VirtualTerminal
~TerminalInput() = default;
bool HandleKey(const IInputEvent* const pInEvent);
void ChangeAnsiMode(const bool ansiMode) noexcept;
void ChangeKeypadMode(const bool applicationMode) noexcept;
void ChangeCursorKeysMode(const bool applicationMode) noexcept;
void ChangeWin32InputMode(const bool win32InputMode) noexcept;
enum class Mode : size_t
{
Ansi,
Keypad,
CursorKey,
Win32,
Utf8MouseEncoding,
SgrMouseEncoding,
DefaultMouseTracking,
ButtonEventMouseTracking,
AnyEventMouseTracking,
AlternateScroll
};
void SetInputMode(const Mode mode, const bool enabled);
bool GetInputMode(const Mode mode) const;
void ForceDisableWin32InputMode(const bool win32InputMode) noexcept;
#pragma region MouseInput
@ -62,14 +77,6 @@ namespace Microsoft::Console::VirtualTerminal
#pragma region MouseInputState Management
// These methods are defined in mouseInputState.cpp
void SetUtf8ExtendedMode(const bool enable) noexcept;
void SetSGRExtendedMode(const bool enable) noexcept;
void EnableDefaultTracking(const bool enable) noexcept;
void EnableButtonEventTracking(const bool enable) noexcept;
void EnableAnyEventTracking(const bool enable) noexcept;
void EnableAlternateScroll(const bool enable) noexcept;
void UseAlternateScreenBuffer() noexcept;
void UseMainScreenBuffer() noexcept;
#pragma endregion
@ -80,10 +87,7 @@ namespace Microsoft::Console::VirtualTerminal
// storage location for the leading surrogate of a utf-16 surrogate pair
std::optional<wchar_t> _leadingSurrogate;
bool _ansiMode{ true };
bool _keypadApplicationMode{ false };
bool _cursorApplicationMode{ false };
bool _win32InputMode{ false };
til::enumset<Mode> _inputMode{ Mode::Ansi };
bool _forceDisableWin32InputMode{ false };
void _SendChar(const wchar_t ch);
@ -94,27 +98,8 @@ namespace Microsoft::Console::VirtualTerminal
#pragma region MouseInputState Management
// These methods are defined in mouseInputState.cpp
enum class ExtendedMode : unsigned int
{
None,
Utf8,
Sgr,
Urxvt
};
enum class TrackingMode : unsigned int
{
None,
Default,
ButtonEvent,
AnyEvent
};
struct MouseInputState
{
ExtendedMode extendedMode{ ExtendedMode::None };
TrackingMode trackingMode{ TrackingMode::None };
bool alternateScroll{ false };
bool inAlternateBuffer{ false };
COORD lastPos{ -1, -1 };
unsigned int lastButton{ 0 };
@ -142,8 +127,8 @@ namespace Microsoft::Console::VirtualTerminal
const short modifierKeyState,
const short delta);
bool _ShouldSendAlternateScroll(const unsigned int button, const short delta) const noexcept;
bool _SendAlternateScroll(const short delta) const noexcept;
bool _ShouldSendAlternateScroll(const unsigned int button, const short delta) const;
bool _SendAlternateScroll(const short delta) const;
static constexpr unsigned int s_GetPressedButton(const MouseButtonState state) noexcept;
#pragma endregion

View file

@ -1101,22 +1101,22 @@ bool OutputStateMachineEngine::_GetOscSetClipboard(const std::wstring_view strin
std::wstring& content,
bool& queryClipboard) const noexcept
{
const size_t pos = string.find(';');
if (pos != std::wstring_view::npos)
const auto pos = string.find(L';');
if (pos == std::wstring_view::npos)
{
const std::wstring_view substr = string.substr(pos + 1);
if (substr == L"?")
{
queryClipboard = true;
return true;
}
else
{
return Base64::s_Decode(substr, content);
}
return false;
}
return false;
const auto substr = string.substr(pos + 1);
if (substr == L"?")
{
queryClipboard = true;
return true;
}
// Log_IfFailed has the following description: "Should be decorated WI_NOEXCEPT, but conflicts with forceinline."
#pragma warning(suppress : 26447) // The function is declared 'noexcept' but calls function 'Log_IfFailed()' which may throw exceptions (f.6).
return SUCCEEDED_LOG(Base64::Decode(substr, content));
}
// Method Description:

View file

@ -4,190 +4,155 @@
#include "precomp.h"
#include "base64.hpp"
#pragma warning(disable : 26446) // Prefer to use gsl::at() instead of unchecked subscript operator (bounds.4).
// I didn't want to handle out of memory errors. There's no reasonable mode of
// operation for this application without the ability to allocate memory anyways.
#pragma warning(disable : 26447) // The function is declared 'noexcept' but calls function '...' which may throw exceptions (f.6).
#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).
using namespace Microsoft::Console::VirtualTerminal;
static const char base64Chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
static const char padChar = '=';
// clang-format off
static constexpr uint8_t decodeTable[128] = {
255 /* NUL */, 255 /* SOH */, 255 /* STX */, 255 /* ETX */, 255 /* EOT */, 255 /* ENQ */, 255 /* ACK */, 255 /* BEL */, 255 /* BS */, 255 /* HT */, 255 /* LF */, 255 /* VT */, 255 /* FF */, 255 /* CR */, 255 /* SO */, 255 /* SI */,
255 /* DLE */, 255 /* DC1 */, 255 /* DC2 */, 255 /* DC3 */, 255 /* DC4 */, 255 /* NAK */, 255 /* SYN */, 255 /* ETB */, 255 /* CAN */, 255 /* EM */, 255 /* SUB */, 255 /* ESC */, 255 /* FS */, 255 /* GS */, 255 /* RS */, 255 /* US */,
255 /* SP */, 255 /* ! */, 255 /* " */, 255 /* # */, 255 /* $ */, 255 /* % */, 255 /* & */, 255 /* ' */, 255 /* ( */, 255 /* ) */, 255 /* * */, 62 /* + */, 255 /* , */, 62 /* - */, 255 /* . */, 63 /* / */,
52 /* 0 */, 53 /* 1 */, 54 /* 2 */, 55 /* 3 */, 56 /* 4 */, 57 /* 5 */, 58 /* 6 */, 59 /* 7 */, 60 /* 8 */, 61 /* 9 */, 255 /* : */, 255 /* ; */, 255 /* < */, 255 /* = */, 255 /* > */, 255 /* ? */,
255 /* @ */, 0 /* A */, 1 /* B */, 2 /* C */, 3 /* D */, 4 /* E */, 5 /* F */, 6 /* G */, 7 /* H */, 8 /* I */, 9 /* J */, 10 /* K */, 11 /* L */, 12 /* M */, 13 /* N */, 14 /* O */,
15 /* P */, 16 /* Q */, 17 /* R */, 18 /* S */, 19 /* T */, 20 /* U */, 21 /* V */, 22 /* W */, 23 /* X */, 24 /* Y */, 25 /* Z */, 255 /* [ */, 255 /* \ */, 255 /* ] */, 255 /* ^ */, 63 /* _ */,
255 /* ` */, 26 /* a */, 27 /* b */, 28 /* c */, 29 /* d */, 30 /* e */, 31 /* f */, 32 /* g */, 33 /* h */, 34 /* i */, 35 /* j */, 36 /* k */, 37 /* l */, 38 /* m */, 39 /* n */, 40 /* o */,
41 /* p */, 42 /* q */, 43 /* r */, 44 /* s */, 45 /* t */, 46 /* u */, 47 /* v */, 48 /* w */, 49 /* x */, 50 /* y */, 51 /* z */, 255 /* { */, 255 /* | */, 255 /* } */, 255 /* ~ */, 255 /* DEL */,
};
// clang-format on
#pragma warning(disable : 26446 26447 26482 26485 26493 26494)
// Routine Description:
// - Encode a string using base64. When there are not enough characters
// for one quantum, paddings are added.
// Arguments:
// - src - String to base64 encode.
// Return Value:
// - the encoded string.
std::wstring Base64::s_Encode(const std::wstring_view src) noexcept
// Decodes an UTF8 string encoded with RFC 4648 (Base64) and returns it as UTF16 in dst.
// It supports both variants of the RFC (base64 and base64url), but
// throws an error for non-alphabet characters, including newlines.
// * Throws an exception for all invalid base64 inputs.
// * Doesn't support whitespace and will throw an exception for such strings.
// * Doesn't validate the number of trailing "=". Those are basically ignored.
// Strings like "YQ===" will be accepted as valid input and simply result in "a".
HRESULT Base64::Decode(const std::wstring_view& src, std::wstring& dst) noexcept
{
std::wstring dst;
wchar_t input[3];
std::string result;
result.resize(((src.size() + 3) / 4) * 3);
const auto len = (src.size() + 2) / 3 * 4;
if (len == 0)
{
return dst;
}
dst.reserve(len);
// in and inEnd may be nullptr if src.empty().
// The remaining code in this function ensures not to read from in if src.empty().
#pragma warning(suppress : 26429) // Symbol 'in' is never tested for nullness, it can be marked as not_null (f.23).
auto in = src.data();
const auto inEnd = in + src.size();
// Sometimes in programming you have to ask yourself what the right offset for a pointer is.
// Is 4 enough? Certainly not. 6 on the other hand is just way too much. Clearly 5 is just right.
//
// In all seriousness however the offset is 5, because the batched loop reads 4 characters at a time,
// a base64 string can end with two "=" and the batched loop doesn't handle any such "=".
// Additionally the while() condition of the batched loop would make a lot more sense if it were using <=,
// but for reasons outlined below it needs to use < so we need to add 1 back again.
// We thus get -4-2+1 which is -5.
//
// There's a special reason we need to use < and not <= for the loop:
// In C++ it's undefined behavior to perform any pointer arithmetic that leads to unallocated memory,
// which is why we can't just write `inEnd - 6` as that might be UB if `src.size()` is less than 6.
// We thus would need write `inEnd - min(6, src.size())` in combination with `<=` for the batched loop.
// But if `src.size()` is actually less than 6 then `inEnd` is equal to the initial `in`, aka: an empty range.
// In such cases we'd enter the batched loop and read from `in` despite us not wanting to enter the loop.
// We can fix the issue by using < instead and adding +1 to the offset.
//
// Yes this works.
const auto inEndBatched = inEnd - std::min<size_t>(5, src.size());
auto iter = src.cbegin();
// Encode each three chars into one quantum (four chars).
while (iter < src.cend() - 2)
// outBeg and out may be nullptr if src.empty().
// The remaining code in this function ensures not to write to out if src.empty().
const auto outBeg = result.data();
#pragma warning(suppress : 26429) // Symbol 'out' is never tested for nullness, it can be marked as not_null (f.23).
auto out = outBeg;
// r is just a generic "remainder" we use to accumulate 4 base64 chars into 3 output bytes.
uint_fast32_t r = 0;
// error is treated as a boolean. If it's not 0 we had an invalid input character.
uint_fast16_t error = 0;
// Capturing r/error by reference produces less optimal assembly.
static constexpr auto accumulate = [](auto& r, auto& error, auto ch) {
// n will be in the range [0, 0x3f] for valid ch
// and exactly 0xff for invalid ch.
const auto n = decodeTable[ch & 0x7f];
// Both ch > 0x7f, as well as n > 0x7f are invalid values and count as an error.
// We can add the error state by checking if any bits ~0x7f are set (which is 0xff80).
error |= (ch | n) & 0xff80;
r = r << 6 | n;
};
// If src.empty() then `in == inEndBatched == nullptr` and this is skipped.
while (in < inEndBatched)
{
input[0] = *iter++;
input[1] = *iter++;
input[2] = *iter++;
dst.push_back(base64Chars[input[0] >> 2]);
dst.push_back(base64Chars[(input[0] & 0x03) << 4 | input[1] >> 4]);
dst.push_back(base64Chars[(input[1] & 0x0f) << 2 | input[2] >> 6]);
dst.push_back(base64Chars[(input[2] & 0x3f)]);
const auto ch0 = *in++;
const auto ch1 = *in++;
const auto ch2 = *in++;
const auto ch3 = *in++;
// Most other base64 libraries do something like this:
// const auto n0 = decodeTable[a];
// const auto n1 = decodeTable[b];
// const auto n2 = decodeTable[c];
// const auto n3 = decodeTable[d];
// *out++ = n0 << 2 | n1 >> 4;
// *out++ = (n1 & 0xf) << 4 | n2 >> 2;
// *out++ = (n2 & 0x3) << 6 | n3;
//
// But on all modern CPUs I tested (well even those 10 years old at this point) shifting base64
// characters into a single register (here: r) is faster than the traditional approach.
// I believe this is due to reducing the dependency of instructions on prior calculations.
accumulate(r, error, ch0);
accumulate(r, error, ch1);
accumulate(r, error, ch2);
accumulate(r, error, ch3);
*out++ = gsl::narrow_cast<char>(r >> 16);
*out++ = gsl::narrow_cast<char>(r >> 8);
*out++ = gsl::narrow_cast<char>(r >> 0);
}
// Here only zero, or one, or two chars are left. We may need to add paddings.
if (iter < src.cend())
{
input[0] = *iter++;
dst.push_back(base64Chars[input[0] >> 2]);
if (iter < src.cend()) // Two chars left.
uint_fast8_t ri = 0;
// If src.empty() then `in == inEnd == nullptr` and this is skipped.
for (; in < inEnd; ++in)
{
input[1] = *iter++;
dst.push_back(base64Chars[(input[0] & 0x03) << 4 | input[1] >> 4]);
dst.push_back(base64Chars[(input[1] & 0x0f) << 2]);
}
else // Only one char left.
{
dst.push_back(base64Chars[(input[0] & 0x03) << 4]);
dst.push_back(padChar);
}
dst.push_back(padChar);
}
return dst;
}
// Routine Description:
// - Decode a base64 string. This requires the base64 string is properly padded.
// Otherwise, false will be returned.
// Arguments:
// - src - String to decode.
// - dst - Destination to decode into.
// Return Value:
// - true if decoding successfully, otherwise false.
bool Base64::s_Decode(const std::wstring_view src, std::wstring& dst) noexcept
{
std::string mbStr;
int state = 0;
char tmp;
const auto len = src.size() / 4 * 3;
if (len == 0)
{
return false;
}
mbStr.reserve(len);
auto iter = src.cbegin();
while (iter < src.cend())
{
if (s_IsSpace(*iter)) // Skip whitespace anywhere.
{
iter++;
continue;
if (const auto ch = *in; ch != '=')
{
accumulate(r, error, ch);
ri++;
}
}
if (*iter == padChar)
switch (ri)
{
break;
}
auto pos = strchr(base64Chars, *iter);
if (!pos) // A non-base64 character found.
{
return false;
}
switch (state)
{
case 0:
tmp = (char)(pos - base64Chars) << 2;
state = 1;
break;
case 1:
tmp |= (char)(pos - base64Chars) >> 4;
mbStr += tmp;
tmp = (char)((pos - base64Chars) & 0x0f) << 4;
state = 2;
break;
case 2:
tmp |= (char)(pos - base64Chars) >> 2;
mbStr += tmp;
tmp = (char)((pos - base64Chars) & 0x03) << 6;
state = 3;
*out++ = gsl::narrow_cast<char>(r >> 4);
break;
case 3:
tmp |= pos - base64Chars;
mbStr += tmp;
state = 0;
break;
default:
break;
}
iter++;
}
if (iter < src.cend()) // Padding char is met.
{
iter++;
switch (state)
{
// Invalid when state is 0 or 1.
case 0:
case 1:
return false;
case 2:
// Skip any number of spaces.
while (iter < src.cend() && s_IsSpace(*iter))
{
iter++;
}
// Make sure there is another trailing padding character.
if (iter == src.cend() || *iter != padChar)
{
return false;
}
iter++; // Skip the padding character and fallthrough to "single trailing padding character" case.
[[fallthrough]];
case 3:
while (iter < src.cend())
{
if (!s_IsSpace(*iter))
{
return false;
}
iter++;
}
*out++ = gsl::narrow_cast<char>(r >> 10);
*out++ = gsl::narrow_cast<char>(r >> 2);
break;
case 4:
*out++ = gsl::narrow_cast<char>(r >> 16);
*out++ = gsl::narrow_cast<char>(r >> 8);
*out++ = gsl::narrow_cast<char>(r >> 0);
break;
default:
error |= ri;
break;
}
}
else if (state != 0) // When no padding, we must be in state 0.
if (error)
{
return false;
return HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
}
return SUCCEEDED(til::u8u16(mbStr, dst));
}
// Routine Description:
// - Check if parameter is a base64 whitespace. Only carriage return or line feed
// is valid whitespace.
// Arguments:
// - ch - Character to check.
// Return Value:
// - true iff ch is a carriage return or line feed.
constexpr bool Base64::s_IsSpace(const wchar_t ch) noexcept
{
return ch == L'\r' || ch == L'\n';
result.resize(out - outBeg);
return til::u8u16(result, dst);
}

View file

@ -16,10 +16,6 @@ namespace Microsoft::Console::VirtualTerminal
class Base64
{
public:
static std::wstring s_Encode(const std::wstring_view src) noexcept;
static bool s_Decode(const std::wstring_view src, std::wstring& dst) noexcept;
private:
static constexpr bool s_IsSpace(const wchar_t ch) noexcept;
static HRESULT Decode(const std::wstring_view& src, std::wstring& dst) noexcept;
};
}

View file

@ -3,7 +3,8 @@
#include "precomp.h"
#include "WexTestClass.h"
#include "../../inc/consoletaeftemplates.hpp"
#include <til/rand.h>
#include "base64.hpp"
@ -28,74 +29,90 @@ class Microsoft::Console::VirtualTerminal::Base64Test
{
TEST_CLASS(Base64Test);
TEST_METHOD(TestBase64Encode)
TEST_METHOD(DecodeFuzz)
{
VERIFY_ARE_EQUAL(L"Zm9v", Base64::s_Encode(L"foo"));
VERIFY_ARE_EQUAL(L"Zm9vYg==", Base64::s_Encode(L"foob"));
VERIFY_ARE_EQUAL(L"Zm9vYmE=", Base64::s_Encode(L"fooba"));
VERIFY_ARE_EQUAL(L"Zm9vYmFy", Base64::s_Encode(L"foobar"));
VERIFY_ARE_EQUAL(L"Zm9vYmFyDQo=", Base64::s_Encode(L"foobar\r\n"));
// NOTE: Modify testRounds to get the feeling of running a fuzz test on Base64::Decode.
static constexpr auto testRounds = 8;
pcg_engines::oneseq_dxsm_64_32 rng{ til::gen_random<uint64_t>() };
// Fills referenceData with random ASCII characters.
// We use ASCII as Base64::Decode uses til:u8u16 internally and I don't want to test that.
char referenceData[128];
{
uint32_t randomData[sizeof(referenceData) / sizeof(uint32_t)];
for (auto& i : randomData)
{
i = rng();
}
const std::string_view randomDataView{ reinterpret_cast<const char*>(randomData), sizeof(randomData) };
auto out = std::begin(referenceData);
for (const auto& ch : randomDataView)
{
*out++ = static_cast<char>(ch & 0x7f);
}
}
wchar_t wideReferenceData[std::size(referenceData)];
std::copy_n(std::begin(referenceData), std::size(referenceData), std::begin(wideReferenceData));
std::wstring encoded;
std::wstring decoded;
for (auto i = 0; i < testRounds; ++i)
{
const auto referenceLength = rng(static_cast<uint32_t>(std::size(referenceData)));
const std::wstring_view wideReference{ std::begin(wideReferenceData), referenceLength };
if (!referenceLength)
{
encoded.clear();
}
else
{
const auto reference = reinterpret_cast<const BYTE*>(std::begin(referenceData));
DWORD encodedLen;
THROW_IF_WIN32_BOOL_FALSE(CryptBinaryToStringW(reference, referenceLength, CRYPT_STRING_BASE64 | CRYPT_STRING_NOCRLF, nullptr, &encodedLen));
// encodedLen is returned by CryptBinaryToStringW including the trailing null byte.
encoded.resize(encodedLen - 1);
THROW_IF_WIN32_BOOL_FALSE(CryptBinaryToStringW(reference, referenceLength, CRYPT_STRING_BASE64 | CRYPT_STRING_NOCRLF, encoded.data(), &encodedLen));
}
// Test whether Decode() handles strings with and without trailing "=".
if (rng(2))
{
while (!encoded.empty() && encoded.back() == '=')
{
encoded.pop_back();
}
}
// Test whether Decode() handles null-pointer arguments correctly.
std::wstring_view encodedView{ encoded };
if (encodedView.empty() && rng(2))
{
encodedView = {};
}
Base64::Decode(encodedView, decoded);
VERIFY_ARE_EQUAL(wideReference, decoded);
}
}
TEST_METHOD(TestBase64Decode)
TEST_METHOD(DecodeUTF8)
{
std::wstring result;
bool success;
success = Base64::s_Decode(L"Zm9v", result);
VERIFY_ARE_EQUAL(true, success);
VERIFY_ARE_EQUAL(L"foo", result);
result = L"";
success = Base64::s_Decode(L"Zm9vYg==", result);
VERIFY_ARE_EQUAL(true, success);
VERIFY_ARE_EQUAL(L"foob", result);
result = L"";
success = Base64::s_Decode(L"Zm9vYmE=", result);
VERIFY_ARE_EQUAL(true, success);
VERIFY_ARE_EQUAL(L"fooba", result);
result = L"";
success = Base64::s_Decode(L"Zm9vYmFy", result);
VERIFY_ARE_EQUAL(true, success);
VERIFY_ARE_EQUAL(L"foobar", result);
result = L"";
success = Base64::s_Decode(L"Zm9vYmFyDQo=", result);
VERIFY_ARE_EQUAL(true, success);
VERIFY_ARE_EQUAL(L"foobar\r\n", result);
result = L"";
success = Base64::s_Decode(L"Zm9v\rYmFy", result);
VERIFY_ARE_EQUAL(true, success);
VERIFY_ARE_EQUAL(L"foobar", result);
result = L"";
success = Base64::s_Decode(L"Zm9v\r\nYmFy\n", result);
VERIFY_ARE_EQUAL(true, success);
VERIFY_ARE_EQUAL(L"foobar", result);
success = Base64::s_Decode(L"Z", result);
VERIFY_ARE_EQUAL(false, success);
success = Base64::s_Decode(L"Zm9vYg", result);
VERIFY_ARE_EQUAL(false, success);
success = Base64::s_Decode(L"Zm9vYg=", result);
VERIFY_ARE_EQUAL(false, success);
// U+306b U+307b U+3093 U+3054 U+6c49 U+8bed U+d55c U+ad6d
result = L"";
success = Base64::s_Decode(L"44Gr44G744KT44GU5rGJ6K+t7ZWc6rWt", result);
VERIFY_ARE_EQUAL(true, success);
Base64::Decode(L"44Gr44G744KT44GU5rGJ6K+t7ZWc6rWt", result);
VERIFY_ARE_EQUAL(L"にほんご汉语한국", result);
// U+d83d U+dc4d U+d83d U+dc4d U+d83c U+dffb U+d83d U+dc4d U+d83c U+dffc U+d83d
// U+dc4d U+d83c U+dffd U+d83d U+dc4d U+d83c U+dffe U+d83d U+dc4d U+d83c U+dfff
result = L"";
success = Base64::s_Decode(L"8J+RjfCfkY3wn4+78J+RjfCfj7zwn5GN8J+PvfCfkY3wn4++8J+RjfCfj78=", result);
VERIFY_ARE_EQUAL(true, success);
Base64::Decode(L"8J+RjfCfkY3wn4+78J+RjfCfj7zwn5GN8J+PvfCfkY3wn4++8J+RjfCfj78=", result);
VERIFY_ARE_EQUAL(L"👍👍🏻👍🏼👍🏽👍🏾👍🏿", result);
}
};

View file

@ -3255,7 +3255,7 @@ class StateMachineExternalTest final
pDispatch->_copyContent = L"UNCHANGED";
// Passing a non-base64 `Pd` param is illegal, won't change the content.
mach.ProcessString(L"\x1b]52;;foo\x07");
mach.ProcessString(L"\x1b]52;;???\x07");
VERIFY_ARE_EQUAL(L"UNCHANGED", pDispatch->_copyContent);
pDispatch->ClearState();