Add support for the VT answerback capability (#17660)

The answerback feature allows for the user to define a message that the
terminal will transmit to the host whenever an `ENQ` (enquiry) control
character is received.

## Detailed Description of the Pull Request / Additional comments

In Windows Terminal, the message can be configured at the profile level
of the settings file, as a string property named `AnswerbackMessage`.

In ConHost, the message can be configured in the registry, again as a
string value with the name `AnswerbackMessage`.

## Validation Steps Performed

I've confirmed that the control is working as intended in both Windows
Terminal and ConHost using Vttest.

Closes #11946
This commit is contained in:
James Holderness 2024-08-07 23:46:01 +01:00 committed by GitHub
parent 2c452e0fd6
commit 746cf1f148
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
24 changed files with 62 additions and 3 deletions

View File

@ -38,6 +38,7 @@ ANSISYS
ANSISYSRC ANSISYSRC
ANSISYSSC ANSISYSSC
answerback answerback
ANSWERBACKMESSAGE
antialiasing antialiasing
ANull ANull
anycpu anycpu

View File

@ -104,6 +104,7 @@ GetConsoleKeyboardLayoutNameW(
#define CONSOLE_REGISTRY_DEFAULTFOREGROUND L"DefaultForeground" #define CONSOLE_REGISTRY_DEFAULTFOREGROUND L"DefaultForeground"
#define CONSOLE_REGISTRY_DEFAULTBACKGROUND L"DefaultBackground" #define CONSOLE_REGISTRY_DEFAULTBACKGROUND L"DefaultBackground"
#define CONSOLE_REGISTRY_TERMINALSCROLLING L"TerminalScrolling" #define CONSOLE_REGISTRY_TERMINALSCROLLING L"TerminalScrolling"
#define CONSOLE_REGISTRY_ANSWERBACKMESSAGE L"AnswerbackMessage"
// end V2 console settings // end V2 console settings
/* /*

View File

@ -3032,6 +3032,10 @@
"description": "By default Windows treats Ctrl+Alt as an alias for AltGr. When altGrAliasing is set to false, this behavior will be disabled.", "description": "By default Windows treats Ctrl+Alt as an alias for AltGr. When altGrAliasing is set to false, this behavior will be disabled.",
"type": "boolean" "type": "boolean"
}, },
"answerbackMessage": {
"description": "The response that is sent when an ENQ control character is received.",
"type": "string"
},
"source": { "source": {
"description": "Stores the name of the profile generator that originated this profile.", "description": "Stores the name of the profile generator that originated this profile.",
"type": [ "type": [

View File

@ -14,6 +14,7 @@ namespace Microsoft.Terminal.Core
Boolean SnapOnInput; Boolean SnapOnInput;
Boolean AltGrAliasing; Boolean AltGrAliasing;
String AnswerbackMessage;
String StartingTitle; String StartingTitle;
Boolean SuppressApplicationTitle; Boolean SuppressApplicationTitle;

View File

@ -82,6 +82,7 @@ void Terminal::UpdateSettings(ICoreSettings settings)
_snapOnInput = settings.SnapOnInput(); _snapOnInput = settings.SnapOnInput();
_altGrAliasing = settings.AltGrAliasing(); _altGrAliasing = settings.AltGrAliasing();
_answerbackMessage = settings.AnswerbackMessage();
_wordDelimiters = settings.WordDelimiters(); _wordDelimiters = settings.WordDelimiters();
_suppressApplicationTitle = settings.SuppressApplicationTitle(); _suppressApplicationTitle = settings.SuppressApplicationTitle();
_startingTitle = settings.StartingTitle(); _startingTitle = settings.StartingTitle();

View File

@ -135,6 +135,7 @@ public:
void SetTextAttributes(const TextAttribute& attrs) noexcept override; void SetTextAttributes(const TextAttribute& attrs) noexcept override;
void SetSystemMode(const Mode mode, const bool enabled) noexcept override; void SetSystemMode(const Mode mode, const bool enabled) noexcept override;
bool GetSystemMode(const Mode mode) const noexcept override; bool GetSystemMode(const Mode mode) const noexcept override;
void ReturnAnswerback() override;
void WarningBell() override; void WarningBell() override;
void SetWindowTitle(const std::wstring_view title) override; void SetWindowTitle(const std::wstring_view title) override;
CursorType GetUserDefaultCursorStyle() const noexcept override; CursorType GetUserDefaultCursorStyle() const noexcept override;
@ -371,6 +372,7 @@ private:
size_t _hyperlinkPatternId = 0; size_t _hyperlinkPatternId = 0;
std::wstring _answerbackMessage;
std::wstring _workingDirectory; std::wstring _workingDirectory;
// This default fake font value is only used to check if the font is a raster font. // This default fake font value is only used to check if the font is a raster font.

View File

@ -71,6 +71,11 @@ bool Terminal::GetSystemMode(const Mode mode) const noexcept
return _systemMode.test(mode); return _systemMode.test(mode);
} }
void Terminal::ReturnAnswerback()
{
ReturnResponse(_answerbackMessage);
}
void Terminal::WarningBell() void Terminal::WarningBell()
{ {
_pfnWarningBell(); _pfnWarningBell();

View File

@ -81,6 +81,7 @@ Author(s):
X(int32_t, HistorySize, "historySize", DEFAULT_HISTORY_SIZE) \ X(int32_t, HistorySize, "historySize", DEFAULT_HISTORY_SIZE) \
X(bool, SnapOnInput, "snapOnInput", true) \ X(bool, SnapOnInput, "snapOnInput", true) \
X(bool, AltGrAliasing, "altGrAliasing", true) \ X(bool, AltGrAliasing, "altGrAliasing", true) \
X(hstring, AnswerbackMessage, "answerbackMessage") \
X(hstring, Commandline, "commandline", L"%SystemRoot%\\System32\\cmd.exe") \ X(hstring, Commandline, "commandline", L"%SystemRoot%\\System32\\cmd.exe") \
X(Microsoft::Terminal::Control::ScrollbarState, ScrollState, "scrollbarState", Microsoft::Terminal::Control::ScrollbarState::Visible) \ X(Microsoft::Terminal::Control::ScrollbarState, ScrollState, "scrollbarState", Microsoft::Terminal::Control::ScrollbarState::Visible) \
X(Microsoft::Terminal::Control::TextAntialiasingMode, AntialiasingMode, "antialiasingMode", Microsoft::Terminal::Control::TextAntialiasingMode::Grayscale) \ X(Microsoft::Terminal::Control::TextAntialiasingMode, AntialiasingMode, "antialiasingMode", Microsoft::Terminal::Control::TextAntialiasingMode::Grayscale) \

View File

@ -73,6 +73,7 @@ namespace Microsoft.Terminal.Settings.Model
INHERITABLE_PROFILE_SETTING(Int32, HistorySize); INHERITABLE_PROFILE_SETTING(Int32, HistorySize);
INHERITABLE_PROFILE_SETTING(Boolean, SnapOnInput); INHERITABLE_PROFILE_SETTING(Boolean, SnapOnInput);
INHERITABLE_PROFILE_SETTING(Boolean, AltGrAliasing); INHERITABLE_PROFILE_SETTING(Boolean, AltGrAliasing);
INHERITABLE_PROFILE_SETTING(String, AnswerbackMessage);
INHERITABLE_PROFILE_SETTING(BellStyle, BellStyle); INHERITABLE_PROFILE_SETTING(BellStyle, BellStyle);
INHERITABLE_PROFILE_SETTING(Windows.Foundation.Collections.IMap<String COMMA String>, EnvironmentVariables); INHERITABLE_PROFILE_SETTING(Windows.Foundation.Collections.IMap<String COMMA String>, EnvironmentVariables);

View File

@ -284,6 +284,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
_HistorySize = profile.HistorySize(); _HistorySize = profile.HistorySize();
_SnapOnInput = profile.SnapOnInput(); _SnapOnInput = profile.SnapOnInput();
_AltGrAliasing = profile.AltGrAliasing(); _AltGrAliasing = profile.AltGrAliasing();
_AnswerbackMessage = profile.AnswerbackMessage();
// Fill in the remaining properties from the profile // Fill in the remaining properties from the profile
_ProfileName = profile.Name(); _ProfileName = profile.Name();

View File

@ -86,6 +86,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
INHERITABLE_SETTING(Model::TerminalSettings, bool, SnapOnInput, true); INHERITABLE_SETTING(Model::TerminalSettings, bool, SnapOnInput, true);
INHERITABLE_SETTING(Model::TerminalSettings, bool, AltGrAliasing, true); INHERITABLE_SETTING(Model::TerminalSettings, bool, AltGrAliasing, true);
INHERITABLE_SETTING(Model::TerminalSettings, hstring, AnswerbackMessage);
INHERITABLE_SETTING(Model::TerminalSettings, til::color, CursorColor, DEFAULT_CURSOR_COLOR); INHERITABLE_SETTING(Model::TerminalSettings, til::color, CursorColor, DEFAULT_CURSOR_COLOR);
INHERITABLE_SETTING(Model::TerminalSettings, Microsoft::Terminal::Core::CursorStyle, CursorShape, Core::CursorStyle::Vintage); INHERITABLE_SETTING(Model::TerminalSettings, Microsoft::Terminal::Core::CursorStyle, CursorShape, Core::CursorStyle::Vintage);
INHERITABLE_SETTING(Model::TerminalSettings, uint32_t, CursorHeight, DEFAULT_CURSOR_HEIGHT); INHERITABLE_SETTING(Model::TerminalSettings, uint32_t, CursorHeight, DEFAULT_CURSOR_HEIGHT);

View File

@ -38,6 +38,7 @@
X(int32_t, InitialCols, 80) \ X(int32_t, InitialCols, 80) \
X(bool, SnapOnInput, true) \ X(bool, SnapOnInput, true) \
X(bool, AltGrAliasing, true) \ X(bool, AltGrAliasing, true) \
X(winrt::hstring, AnswerbackMessage) \
X(winrt::hstring, WordDelimiters, DEFAULT_WORD_DELIMITERS) \ X(winrt::hstring, WordDelimiters, DEFAULT_WORD_DELIMITERS) \
X(bool, CopyOnSelect, false) \ X(bool, CopyOnSelect, false) \
X(bool, FocusFollowMouse, false) \ X(bool, FocusFollowMouse, false) \

View File

@ -146,6 +146,16 @@ bool ConhostInternalGetSet::GetSystemMode(const Mode mode) const
} }
} }
// Routine Description:
// - Sends the configured answerback message in response to an ENQ query.
// Return Value:
// - <none>
void ConhostInternalGetSet::ReturnAnswerback()
{
const auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
ReturnResponse(gci.GetAnswerbackMessage());
}
// Routine Description: // Routine Description:
// - Sends a notify message to play the "SystemHand" sound event. // - Sends a notify message to play the "SystemHand" sound event.
// Return Value: // Return Value:

View File

@ -40,6 +40,7 @@ public:
void SetSystemMode(const Mode mode, const bool enabled) override; void SetSystemMode(const Mode mode, const bool enabled) override;
bool GetSystemMode(const Mode mode) const override; bool GetSystemMode(const Mode mode) const override;
void ReturnAnswerback() override;
void WarningBell() override; void WarningBell() override;
void SetWindowTitle(const std::wstring_view title) override; void SetWindowTitle(const std::wstring_view title) override;

View File

@ -765,6 +765,11 @@ void Settings::SetTerminalScrolling(const bool terminalScrollingEnabled) noexcep
_TerminalScrolling = terminalScrollingEnabled; _TerminalScrolling = terminalScrollingEnabled;
} }
std::wstring_view Settings::GetAnswerbackMessage() const noexcept
{
return _answerbackMessage;
}
// Determines whether our primary renderer should be DirectX or GDI. // Determines whether our primary renderer should be DirectX or GDI.
// This is based on user preference and velocity hold back state. // This is based on user preference and velocity hold back state.
bool Settings::GetUseDx() const noexcept bool Settings::GetUseDx() const noexcept

View File

@ -176,6 +176,8 @@ public:
bool IsTerminalScrolling() const noexcept; bool IsTerminalScrolling() const noexcept;
void SetTerminalScrolling(const bool terminalScrollingEnabled) noexcept; void SetTerminalScrolling(const bool terminalScrollingEnabled) noexcept;
std::wstring_view GetAnswerbackMessage() const noexcept;
bool GetUseDx() const noexcept; bool GetUseDx() const noexcept;
bool GetCopyColor() const noexcept; bool GetCopyColor() const noexcept;
SettingsTextMeasurementMode GetTextMeasurementMode() const noexcept; SettingsTextMeasurementMode GetTextMeasurementMode() const noexcept;
@ -236,5 +238,6 @@ private:
bool _fInterceptCopyPaste; bool _fInterceptCopyPaste;
bool _TerminalScrolling; bool _TerminalScrolling;
WCHAR _answerbackMessage[32] = {};
friend class RegistrySerialization; friend class RegistrySerialization;
}; };

View File

@ -60,6 +60,7 @@ const RegistrySerialization::_RegPropertyMap RegistrySerialization::s_PropertyMa
{ _RegPropertyType::Dword, CONSOLE_REGISTRY_CURSORTYPE, SET_FIELD_AND_SIZE(_CursorType) }, { _RegPropertyType::Dword, CONSOLE_REGISTRY_CURSORTYPE, SET_FIELD_AND_SIZE(_CursorType) },
{ _RegPropertyType::Boolean, CONSOLE_REGISTRY_INTERCEPTCOPYPASTE, SET_FIELD_AND_SIZE(_fInterceptCopyPaste) }, { _RegPropertyType::Boolean, CONSOLE_REGISTRY_INTERCEPTCOPYPASTE, SET_FIELD_AND_SIZE(_fInterceptCopyPaste) },
{ _RegPropertyType::Boolean, CONSOLE_REGISTRY_TERMINALSCROLLING, SET_FIELD_AND_SIZE(_TerminalScrolling) }, { _RegPropertyType::Boolean, CONSOLE_REGISTRY_TERMINALSCROLLING, SET_FIELD_AND_SIZE(_TerminalScrolling) },
{ _RegPropertyType::String, CONSOLE_REGISTRY_ANSWERBACKMESSAGE, SET_FIELD_AND_SIZE(_answerbackMessage) },
{ _RegPropertyType::Boolean, CONSOLE_REGISTRY_USEDX, SET_FIELD_AND_SIZE(_fUseDx) }, { _RegPropertyType::Boolean, CONSOLE_REGISTRY_USEDX, SET_FIELD_AND_SIZE(_fUseDx) },
{ _RegPropertyType::Boolean, CONSOLE_REGISTRY_COPYCOLOR, SET_FIELD_AND_SIZE(_fCopyColor) }, { _RegPropertyType::Boolean, CONSOLE_REGISTRY_COPYCOLOR, SET_FIELD_AND_SIZE(_fCopyColor) },
{ _RegPropertyType::Dword, L"TextMeasurement", SET_FIELD_AND_SIZE(_textMeasurement) }, { _RegPropertyType::Dword, L"TextMeasurement", SET_FIELD_AND_SIZE(_textMeasurement) },

View File

@ -63,6 +63,7 @@ public:
virtual bool SetAnsiMode(const bool ansiMode) = 0; // DECANM virtual bool SetAnsiMode(const bool ansiMode) = 0; // DECANM
virtual bool SetTopBottomScrollingMargins(const VTInt topMargin, const VTInt bottomMargin) = 0; // DECSTBM virtual bool SetTopBottomScrollingMargins(const VTInt topMargin, const VTInt bottomMargin) = 0; // DECSTBM
virtual bool SetLeftRightScrollingMargins(const VTInt leftMargin, const VTInt rightMargin) = 0; // DECSLRM virtual bool SetLeftRightScrollingMargins(const VTInt leftMargin, const VTInt rightMargin) = 0; // DECSLRM
virtual bool EnquireAnswerback() = 0; // ENQ
virtual bool WarningBell() = 0; // BEL virtual bool WarningBell() = 0; // BEL
virtual bool CarriageReturn() = 0; // CR virtual bool CarriageReturn() = 0; // CR
virtual bool LineFeed(const DispatchTypes::LineFeedType lineFeedType) = 0; // IND, NEL, LF, FF, VT virtual bool LineFeed(const DispatchTypes::LineFeedType lineFeedType) = 0; // IND, NEL, LF, FF, VT

View File

@ -64,6 +64,7 @@ namespace Microsoft::Console::VirtualTerminal
virtual void SetSystemMode(const Mode mode, const bool enabled) = 0; virtual void SetSystemMode(const Mode mode, const bool enabled) = 0;
virtual bool GetSystemMode(const Mode mode) const = 0; virtual bool GetSystemMode(const Mode mode) const = 0;
virtual void ReturnAnswerback() = 0;
virtual void WarningBell() = 0; virtual void WarningBell() = 0;
virtual void SetWindowTitle(const std::wstring_view title) = 0; virtual void SetWindowTitle(const std::wstring_view title) = 0;
virtual void UseAlternateScreenBuffer(const TextAttribute& attrs) = 0; virtual void UseAlternateScreenBuffer(const TextAttribute& attrs) = 0;

View File

@ -2476,6 +2476,18 @@ bool AdaptDispatch::SetLeftRightScrollingMargins(const VTInt leftMargin,
return true; return true;
} }
// Routine Description:
// - ENQ - Directs the terminal to send the answerback message.
// Arguments:
// - None
// Return Value:
// - True.
bool AdaptDispatch::EnquireAnswerback()
{
_api.ReturnAnswerback();
return true;
}
// Routine Description: // Routine Description:
// - BEL - Rings the warning bell. // - BEL - Rings the warning bell.
// Causes the terminal to emit an audible tone of brief duration. // Causes the terminal to emit an audible tone of brief duration.

View File

@ -101,6 +101,7 @@ namespace Microsoft::Console::VirtualTerminal
const VTInt bottomMargin) override; // DECSTBM const VTInt bottomMargin) override; // DECSTBM
bool SetLeftRightScrollingMargins(const VTInt leftMargin, bool SetLeftRightScrollingMargins(const VTInt leftMargin,
const VTInt rightMargin) override; // DECSLRM const VTInt rightMargin) override; // DECSLRM
bool EnquireAnswerback() override; // ENQ
bool WarningBell() override; // BEL bool WarningBell() override; // BEL
bool CarriageReturn() override; // CR bool CarriageReturn() override; // CR
bool LineFeed(const DispatchTypes::LineFeedType lineFeedType) override; // IND, NEL, LF, FF, VT bool LineFeed(const DispatchTypes::LineFeedType lineFeedType) override; // IND, NEL, LF, FF, VT

View File

@ -56,6 +56,7 @@ public:
bool SetAnsiMode(const bool /*ansiMode*/) override { return false; } // DECANM bool SetAnsiMode(const bool /*ansiMode*/) override { return false; } // DECANM
bool SetTopBottomScrollingMargins(const VTInt /*topMargin*/, const VTInt /*bottomMargin*/) override { return false; } // DECSTBM bool SetTopBottomScrollingMargins(const VTInt /*topMargin*/, const VTInt /*bottomMargin*/) override { return false; } // DECSTBM
bool SetLeftRightScrollingMargins(const VTInt /*leftMargin*/, const VTInt /*rightMargin*/) override { return false; } // DECSLRM bool SetLeftRightScrollingMargins(const VTInt /*leftMargin*/, const VTInt /*rightMargin*/) override { return false; } // DECSLRM
bool EnquireAnswerback() override { return false; } // ENQ
bool WarningBell() override { return false; } // BEL bool WarningBell() override { return false; } // BEL
bool CarriageReturn() override { return false; } // CR bool CarriageReturn() override { return false; } // CR
bool LineFeed(const DispatchTypes::LineFeedType /*lineFeedType*/) override { return false; } // IND, NEL, LF, FF, VT bool LineFeed(const DispatchTypes::LineFeedType /*lineFeedType*/) override { return false; } // IND, NEL, LF, FF, VT

View File

@ -118,6 +118,11 @@ public:
return _systemMode.test(mode); return _systemMode.test(mode);
} }
void ReturnAnswerback()
{
Log::Comment(L"ReturnAnswerback MOCK called...");
}
void WarningBell() override void WarningBell() override
{ {
Log::Comment(L"WarningBell MOCK called..."); Log::Comment(L"WarningBell MOCK called...");

View File

@ -49,9 +49,7 @@ bool OutputStateMachineEngine::ActionExecute(const wchar_t wch)
switch (wch) switch (wch)
{ {
case AsciiChars::ENQ: case AsciiChars::ENQ:
// GH#11946: At some point we may want to add support for the VT _dispatch->EnquireAnswerback();
// answerback feature, which requires responding to an ENQ control
// with a user-defined reply, but until then we just ignore it.
break; break;
case AsciiChars::BEL: case AsciiChars::BEL:
_dispatch->WarningBell(); _dispatch->WarningBell();