From 746cf1f1487c0277971a0dedc6ad9af6ec78abd3 Mon Sep 17 00:00:00 2001 From: James Holderness Date: Wed, 7 Aug 2024 23:46:01 +0100 Subject: [PATCH] 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 --- .github/actions/spelling/expect/expect.txt | 1 + dep/Console/winconp.h | 1 + doc/cascadia/profiles.schema.json | 4 ++++ src/cascadia/TerminalCore/ICoreSettings.idl | 1 + src/cascadia/TerminalCore/Terminal.cpp | 1 + src/cascadia/TerminalCore/Terminal.hpp | 2 ++ src/cascadia/TerminalCore/TerminalApi.cpp | 5 +++++ src/cascadia/TerminalSettingsModel/MTSMSettings.h | 1 + src/cascadia/TerminalSettingsModel/Profile.idl | 1 + .../TerminalSettingsModel/TerminalSettings.cpp | 1 + .../TerminalSettingsModel/TerminalSettings.h | 1 + src/cascadia/inc/ControlProperties.h | 1 + src/host/outputStream.cpp | 10 ++++++++++ src/host/outputStream.hpp | 1 + src/host/settings.cpp | 5 +++++ src/host/settings.hpp | 3 +++ src/propslib/RegistrySerialization.cpp | 1 + src/terminal/adapter/ITermDispatch.hpp | 1 + src/terminal/adapter/ITerminalApi.hpp | 1 + src/terminal/adapter/adaptDispatch.cpp | 12 ++++++++++++ src/terminal/adapter/adaptDispatch.hpp | 1 + src/terminal/adapter/termDispatch.hpp | 1 + src/terminal/adapter/ut_adapter/adapterTest.cpp | 5 +++++ src/terminal/parser/OutputStateMachineEngine.cpp | 4 +--- 24 files changed, 62 insertions(+), 3 deletions(-) diff --git a/.github/actions/spelling/expect/expect.txt b/.github/actions/spelling/expect/expect.txt index fd80a86e42..720fc383b0 100644 --- a/.github/actions/spelling/expect/expect.txt +++ b/.github/actions/spelling/expect/expect.txt @@ -38,6 +38,7 @@ ANSISYS ANSISYSRC ANSISYSSC answerback +ANSWERBACKMESSAGE antialiasing ANull anycpu diff --git a/dep/Console/winconp.h b/dep/Console/winconp.h index 23fba76c14..8c247eaf3f 100644 --- a/dep/Console/winconp.h +++ b/dep/Console/winconp.h @@ -104,6 +104,7 @@ GetConsoleKeyboardLayoutNameW( #define CONSOLE_REGISTRY_DEFAULTFOREGROUND L"DefaultForeground" #define CONSOLE_REGISTRY_DEFAULTBACKGROUND L"DefaultBackground" #define CONSOLE_REGISTRY_TERMINALSCROLLING L"TerminalScrolling" +#define CONSOLE_REGISTRY_ANSWERBACKMESSAGE L"AnswerbackMessage" // end V2 console settings /* diff --git a/doc/cascadia/profiles.schema.json b/doc/cascadia/profiles.schema.json index 931661698c..e851d3c284 100644 --- a/doc/cascadia/profiles.schema.json +++ b/doc/cascadia/profiles.schema.json @@ -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.", "type": "boolean" }, + "answerbackMessage": { + "description": "The response that is sent when an ENQ control character is received.", + "type": "string" + }, "source": { "description": "Stores the name of the profile generator that originated this profile.", "type": [ diff --git a/src/cascadia/TerminalCore/ICoreSettings.idl b/src/cascadia/TerminalCore/ICoreSettings.idl index 115fdce5a6..112a6f6b1f 100644 --- a/src/cascadia/TerminalCore/ICoreSettings.idl +++ b/src/cascadia/TerminalCore/ICoreSettings.idl @@ -14,6 +14,7 @@ namespace Microsoft.Terminal.Core Boolean SnapOnInput; Boolean AltGrAliasing; + String AnswerbackMessage; String StartingTitle; Boolean SuppressApplicationTitle; diff --git a/src/cascadia/TerminalCore/Terminal.cpp b/src/cascadia/TerminalCore/Terminal.cpp index 7f557c1ac5..a7530807d0 100644 --- a/src/cascadia/TerminalCore/Terminal.cpp +++ b/src/cascadia/TerminalCore/Terminal.cpp @@ -82,6 +82,7 @@ void Terminal::UpdateSettings(ICoreSettings settings) _snapOnInput = settings.SnapOnInput(); _altGrAliasing = settings.AltGrAliasing(); + _answerbackMessage = settings.AnswerbackMessage(); _wordDelimiters = settings.WordDelimiters(); _suppressApplicationTitle = settings.SuppressApplicationTitle(); _startingTitle = settings.StartingTitle(); diff --git a/src/cascadia/TerminalCore/Terminal.hpp b/src/cascadia/TerminalCore/Terminal.hpp index 21d16e1d91..3991a8d17b 100644 --- a/src/cascadia/TerminalCore/Terminal.hpp +++ b/src/cascadia/TerminalCore/Terminal.hpp @@ -135,6 +135,7 @@ public: void SetTextAttributes(const TextAttribute& attrs) noexcept override; void SetSystemMode(const Mode mode, const bool enabled) noexcept override; bool GetSystemMode(const Mode mode) const noexcept override; + void ReturnAnswerback() override; void WarningBell() override; void SetWindowTitle(const std::wstring_view title) override; CursorType GetUserDefaultCursorStyle() const noexcept override; @@ -371,6 +372,7 @@ private: size_t _hyperlinkPatternId = 0; + std::wstring _answerbackMessage; std::wstring _workingDirectory; // This default fake font value is only used to check if the font is a raster font. diff --git a/src/cascadia/TerminalCore/TerminalApi.cpp b/src/cascadia/TerminalCore/TerminalApi.cpp index 5982d184c5..5e94972d58 100644 --- a/src/cascadia/TerminalCore/TerminalApi.cpp +++ b/src/cascadia/TerminalCore/TerminalApi.cpp @@ -71,6 +71,11 @@ bool Terminal::GetSystemMode(const Mode mode) const noexcept return _systemMode.test(mode); } +void Terminal::ReturnAnswerback() +{ + ReturnResponse(_answerbackMessage); +} + void Terminal::WarningBell() { _pfnWarningBell(); diff --git a/src/cascadia/TerminalSettingsModel/MTSMSettings.h b/src/cascadia/TerminalSettingsModel/MTSMSettings.h index ad0eaad4c0..397a852c58 100644 --- a/src/cascadia/TerminalSettingsModel/MTSMSettings.h +++ b/src/cascadia/TerminalSettingsModel/MTSMSettings.h @@ -81,6 +81,7 @@ Author(s): X(int32_t, HistorySize, "historySize", DEFAULT_HISTORY_SIZE) \ X(bool, SnapOnInput, "snapOnInput", true) \ X(bool, AltGrAliasing, "altGrAliasing", true) \ + X(hstring, AnswerbackMessage, "answerbackMessage") \ 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::TextAntialiasingMode, AntialiasingMode, "antialiasingMode", Microsoft::Terminal::Control::TextAntialiasingMode::Grayscale) \ diff --git a/src/cascadia/TerminalSettingsModel/Profile.idl b/src/cascadia/TerminalSettingsModel/Profile.idl index e99ab3c0b8..1ba21c4086 100644 --- a/src/cascadia/TerminalSettingsModel/Profile.idl +++ b/src/cascadia/TerminalSettingsModel/Profile.idl @@ -73,6 +73,7 @@ namespace Microsoft.Terminal.Settings.Model INHERITABLE_PROFILE_SETTING(Int32, HistorySize); INHERITABLE_PROFILE_SETTING(Boolean, SnapOnInput); INHERITABLE_PROFILE_SETTING(Boolean, AltGrAliasing); + INHERITABLE_PROFILE_SETTING(String, AnswerbackMessage); INHERITABLE_PROFILE_SETTING(BellStyle, BellStyle); INHERITABLE_PROFILE_SETTING(Windows.Foundation.Collections.IMap, EnvironmentVariables); diff --git a/src/cascadia/TerminalSettingsModel/TerminalSettings.cpp b/src/cascadia/TerminalSettingsModel/TerminalSettings.cpp index 52b3d030f9..9e4c0ba986 100644 --- a/src/cascadia/TerminalSettingsModel/TerminalSettings.cpp +++ b/src/cascadia/TerminalSettingsModel/TerminalSettings.cpp @@ -284,6 +284,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation _HistorySize = profile.HistorySize(); _SnapOnInput = profile.SnapOnInput(); _AltGrAliasing = profile.AltGrAliasing(); + _AnswerbackMessage = profile.AnswerbackMessage(); // Fill in the remaining properties from the profile _ProfileName = profile.Name(); diff --git a/src/cascadia/TerminalSettingsModel/TerminalSettings.h b/src/cascadia/TerminalSettingsModel/TerminalSettings.h index ee4e0ca939..dc478ddb83 100644 --- a/src/cascadia/TerminalSettingsModel/TerminalSettings.h +++ b/src/cascadia/TerminalSettingsModel/TerminalSettings.h @@ -86,6 +86,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation INHERITABLE_SETTING(Model::TerminalSettings, bool, SnapOnInput, 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, Microsoft::Terminal::Core::CursorStyle, CursorShape, Core::CursorStyle::Vintage); INHERITABLE_SETTING(Model::TerminalSettings, uint32_t, CursorHeight, DEFAULT_CURSOR_HEIGHT); diff --git a/src/cascadia/inc/ControlProperties.h b/src/cascadia/inc/ControlProperties.h index 6f70a868bf..4698045f9e 100644 --- a/src/cascadia/inc/ControlProperties.h +++ b/src/cascadia/inc/ControlProperties.h @@ -38,6 +38,7 @@ X(int32_t, InitialCols, 80) \ X(bool, SnapOnInput, true) \ X(bool, AltGrAliasing, true) \ + X(winrt::hstring, AnswerbackMessage) \ X(winrt::hstring, WordDelimiters, DEFAULT_WORD_DELIMITERS) \ X(bool, CopyOnSelect, false) \ X(bool, FocusFollowMouse, false) \ diff --git a/src/host/outputStream.cpp b/src/host/outputStream.cpp index a719f7a63c..5edd6927c4 100644 --- a/src/host/outputStream.cpp +++ b/src/host/outputStream.cpp @@ -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: +// - +void ConhostInternalGetSet::ReturnAnswerback() +{ + const auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); + ReturnResponse(gci.GetAnswerbackMessage()); +} + // Routine Description: // - Sends a notify message to play the "SystemHand" sound event. // Return Value: diff --git a/src/host/outputStream.hpp b/src/host/outputStream.hpp index 866a6f643d..27ad3c025d 100644 --- a/src/host/outputStream.hpp +++ b/src/host/outputStream.hpp @@ -40,6 +40,7 @@ public: void SetSystemMode(const Mode mode, const bool enabled) override; bool GetSystemMode(const Mode mode) const override; + void ReturnAnswerback() override; void WarningBell() override; void SetWindowTitle(const std::wstring_view title) override; diff --git a/src/host/settings.cpp b/src/host/settings.cpp index e0b07e38f7..e2dd84b78e 100644 --- a/src/host/settings.cpp +++ b/src/host/settings.cpp @@ -765,6 +765,11 @@ void Settings::SetTerminalScrolling(const bool terminalScrollingEnabled) noexcep _TerminalScrolling = terminalScrollingEnabled; } +std::wstring_view Settings::GetAnswerbackMessage() const noexcept +{ + return _answerbackMessage; +} + // Determines whether our primary renderer should be DirectX or GDI. // This is based on user preference and velocity hold back state. bool Settings::GetUseDx() const noexcept diff --git a/src/host/settings.hpp b/src/host/settings.hpp index 94c38afbf4..04b169a7b7 100644 --- a/src/host/settings.hpp +++ b/src/host/settings.hpp @@ -176,6 +176,8 @@ public: bool IsTerminalScrolling() const noexcept; void SetTerminalScrolling(const bool terminalScrollingEnabled) noexcept; + std::wstring_view GetAnswerbackMessage() const noexcept; + bool GetUseDx() const noexcept; bool GetCopyColor() const noexcept; SettingsTextMeasurementMode GetTextMeasurementMode() const noexcept; @@ -236,5 +238,6 @@ private: bool _fInterceptCopyPaste; bool _TerminalScrolling; + WCHAR _answerbackMessage[32] = {}; friend class RegistrySerialization; }; diff --git a/src/propslib/RegistrySerialization.cpp b/src/propslib/RegistrySerialization.cpp index 37ff1c3471..4830a54911 100644 --- a/src/propslib/RegistrySerialization.cpp +++ b/src/propslib/RegistrySerialization.cpp @@ -60,6 +60,7 @@ const RegistrySerialization::_RegPropertyMap RegistrySerialization::s_PropertyMa { _RegPropertyType::Dword, CONSOLE_REGISTRY_CURSORTYPE, SET_FIELD_AND_SIZE(_CursorType) }, { _RegPropertyType::Boolean, CONSOLE_REGISTRY_INTERCEPTCOPYPASTE, SET_FIELD_AND_SIZE(_fInterceptCopyPaste) }, { _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_COPYCOLOR, SET_FIELD_AND_SIZE(_fCopyColor) }, { _RegPropertyType::Dword, L"TextMeasurement", SET_FIELD_AND_SIZE(_textMeasurement) }, diff --git a/src/terminal/adapter/ITermDispatch.hpp b/src/terminal/adapter/ITermDispatch.hpp index a7af79a095..01313efb8b 100644 --- a/src/terminal/adapter/ITermDispatch.hpp +++ b/src/terminal/adapter/ITermDispatch.hpp @@ -63,6 +63,7 @@ public: virtual bool SetAnsiMode(const bool ansiMode) = 0; // DECANM virtual bool SetTopBottomScrollingMargins(const VTInt topMargin, const VTInt bottomMargin) = 0; // DECSTBM virtual bool SetLeftRightScrollingMargins(const VTInt leftMargin, const VTInt rightMargin) = 0; // DECSLRM + virtual bool EnquireAnswerback() = 0; // ENQ virtual bool WarningBell() = 0; // BEL virtual bool CarriageReturn() = 0; // CR virtual bool LineFeed(const DispatchTypes::LineFeedType lineFeedType) = 0; // IND, NEL, LF, FF, VT diff --git a/src/terminal/adapter/ITerminalApi.hpp b/src/terminal/adapter/ITerminalApi.hpp index 0cb900626f..ec6e185901 100644 --- a/src/terminal/adapter/ITerminalApi.hpp +++ b/src/terminal/adapter/ITerminalApi.hpp @@ -64,6 +64,7 @@ namespace Microsoft::Console::VirtualTerminal virtual void SetSystemMode(const Mode mode, const bool enabled) = 0; virtual bool GetSystemMode(const Mode mode) const = 0; + virtual void ReturnAnswerback() = 0; virtual void WarningBell() = 0; virtual void SetWindowTitle(const std::wstring_view title) = 0; virtual void UseAlternateScreenBuffer(const TextAttribute& attrs) = 0; diff --git a/src/terminal/adapter/adaptDispatch.cpp b/src/terminal/adapter/adaptDispatch.cpp index bd1a0d37c5..2ff55fb7b2 100644 --- a/src/terminal/adapter/adaptDispatch.cpp +++ b/src/terminal/adapter/adaptDispatch.cpp @@ -2476,6 +2476,18 @@ bool AdaptDispatch::SetLeftRightScrollingMargins(const VTInt leftMargin, 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: // - BEL - Rings the warning bell. // Causes the terminal to emit an audible tone of brief duration. diff --git a/src/terminal/adapter/adaptDispatch.hpp b/src/terminal/adapter/adaptDispatch.hpp index 8034190022..8d8cc07e0c 100644 --- a/src/terminal/adapter/adaptDispatch.hpp +++ b/src/terminal/adapter/adaptDispatch.hpp @@ -101,6 +101,7 @@ namespace Microsoft::Console::VirtualTerminal const VTInt bottomMargin) override; // DECSTBM bool SetLeftRightScrollingMargins(const VTInt leftMargin, const VTInt rightMargin) override; // DECSLRM + bool EnquireAnswerback() override; // ENQ bool WarningBell() override; // BEL bool CarriageReturn() override; // CR bool LineFeed(const DispatchTypes::LineFeedType lineFeedType) override; // IND, NEL, LF, FF, VT diff --git a/src/terminal/adapter/termDispatch.hpp b/src/terminal/adapter/termDispatch.hpp index 1b8ac3986f..cce60582fd 100644 --- a/src/terminal/adapter/termDispatch.hpp +++ b/src/terminal/adapter/termDispatch.hpp @@ -56,6 +56,7 @@ public: bool SetAnsiMode(const bool /*ansiMode*/) override { return false; } // DECANM bool SetTopBottomScrollingMargins(const VTInt /*topMargin*/, const VTInt /*bottomMargin*/) override { return false; } // DECSTBM bool SetLeftRightScrollingMargins(const VTInt /*leftMargin*/, const VTInt /*rightMargin*/) override { return false; } // DECSLRM + bool EnquireAnswerback() override { return false; } // ENQ bool WarningBell() override { return false; } // BEL bool CarriageReturn() override { return false; } // CR bool LineFeed(const DispatchTypes::LineFeedType /*lineFeedType*/) override { return false; } // IND, NEL, LF, FF, VT diff --git a/src/terminal/adapter/ut_adapter/adapterTest.cpp b/src/terminal/adapter/ut_adapter/adapterTest.cpp index 326859a51c..e65788bc28 100644 --- a/src/terminal/adapter/ut_adapter/adapterTest.cpp +++ b/src/terminal/adapter/ut_adapter/adapterTest.cpp @@ -118,6 +118,11 @@ public: return _systemMode.test(mode); } + void ReturnAnswerback() + { + Log::Comment(L"ReturnAnswerback MOCK called..."); + } + void WarningBell() override { Log::Comment(L"WarningBell MOCK called..."); diff --git a/src/terminal/parser/OutputStateMachineEngine.cpp b/src/terminal/parser/OutputStateMachineEngine.cpp index 82576afd54..38036ce6da 100644 --- a/src/terminal/parser/OutputStateMachineEngine.cpp +++ b/src/terminal/parser/OutputStateMachineEngine.cpp @@ -49,9 +49,7 @@ bool OutputStateMachineEngine::ActionExecute(const wchar_t wch) switch (wch) { case AsciiChars::ENQ: - // GH#11946: At some point we may want to add support for the VT - // answerback feature, which requires responding to an ENQ control - // with a user-defined reply, but until then we just ignore it. + _dispatch->EnquireAnswerback(); break; case AsciiChars::BEL: _dispatch->WarningBell();