diff --git a/.github/actions/spelling/expect/expect.txt b/.github/actions/spelling/expect/expect.txt index 56f0bb4211c..e9df7afcf6d 100644 --- a/.github/actions/spelling/expect/expect.txt +++ b/.github/actions/spelling/expect/expect.txt @@ -1988,3 +1988,4 @@ ZCtrl zero'd ZWJs ZYXWVUTd +XTVERSION diff --git a/doc/reference/master-sequence-list.csv b/doc/reference/master-sequence-list.csv index 85f81dc7e9d..9da4fd38112 100644 --- a/doc/reference/master-sequence-list.csv +++ b/doc/reference/master-sequence-list.csv @@ -26,6 +26,7 @@ "DA1","","Primary Device Attributes","`VT100`","Terminal Management Functions","Identification, status, and Initialization","","","" "DA2","","Secondary Device Attributes","`VT220`","Terminal Management Functions","Identification, status, and Initialization","","","" "DA3","","Tertiary Device Attributes","`VT420`","Terminal Management Functions","Identification, status, and Initialization","","","" +"XTVERSION","","Report Terminal Name and Version","`XTerm`","Terminal Management Functions","Identification, status, and Initialization","","","" "DSR","","Device Status Report","`VT100`","Terminal Management Functions","Identification, status, and Initialization","","","" "DECID","","Identify Device","`VT100`","Terminal Management Functions","Identification, status, and Initialization","","","" "DECTID","","Select Terminal ID","`VT510`","Terminal Management Functions","Identification, status, and Initialization","","","" diff --git a/src/cascadia/TerminalCore/Terminal.hpp b/src/cascadia/TerminalCore/Terminal.hpp index f3330544fca..5be13cdf71e 100644 --- a/src/cascadia/TerminalCore/Terminal.hpp +++ b/src/cascadia/TerminalCore/Terminal.hpp @@ -165,6 +165,8 @@ class Microsoft::Terminal::Core::Terminal final : void SearchMissingCommand(const std::wstring_view command) override; + std::wstring_view GetHostIdentity() const override; + #pragma endregion void ClearMark(); diff --git a/src/cascadia/TerminalCore/TerminalApi.cpp b/src/cascadia/TerminalCore/TerminalApi.cpp index 3030e3b8bab..eee2e2b50cc 100644 --- a/src/cascadia/TerminalCore/TerminalApi.cpp +++ b/src/cascadia/TerminalCore/TerminalApi.cpp @@ -6,6 +6,7 @@ #include "tracing.hpp" #include "../src/inc/unicode.hpp" +#include using namespace Microsoft::Terminal::Core; using namespace Microsoft::Console::Render; @@ -421,3 +422,22 @@ void Terminal::NotifyShellIntegrationMark() // Notify the scrollbar that marks have been added so it can refresh the mark indicators _NotifyScrollEvent(); } + +std::wstring_view Terminal::GetHostIdentity() const +{ + static const auto identity = []() -> std::wstring { + try + { + auto package = winrt::Windows::ApplicationModel::Package::Current(); + auto version = package.Id().Version(); + return fmt::format(FMT_COMPILE(L"Windows Terminal {}.{}.{}.{}"), version.Major, version.Minor, version.Build, version.Revision); + } + catch (...) + { + // Not packaged or other error - return fixed name + return L"Windows Terminal"; + } + }(); + + return identity; +} diff --git a/src/host/outputStream.cpp b/src/host/outputStream.cpp index df07c620d65..8d1f3fdd1da 100644 --- a/src/host/outputStream.cpp +++ b/src/host/outputStream.cpp @@ -459,3 +459,8 @@ void ConhostInternalGetSet::SearchMissingCommand(std::wstring_view /*missingComm { // Not implemented for conhost. } + +std::wstring_view ConhostInternalGetSet::GetHostIdentity() const +{ + return L"Windows Console Host"; +} diff --git a/src/host/outputStream.hpp b/src/host/outputStream.hpp index 2591e430481..2d3cdb613c3 100644 --- a/src/host/outputStream.hpp +++ b/src/host/outputStream.hpp @@ -74,6 +74,8 @@ class ConhostInternalGetSet final : public Microsoft::Console::VirtualTerminal:: void SearchMissingCommand(std::wstring_view missingCommand) override; + std::wstring_view GetHostIdentity() const override; + private: Microsoft::Console::IIoProvider& _io; }; diff --git a/src/terminal/adapter/ITermDispatch.hpp b/src/terminal/adapter/ITermDispatch.hpp index 2a06541547d..5f2de8e580f 100644 --- a/src/terminal/adapter/ITermDispatch.hpp +++ b/src/terminal/adapter/ITermDispatch.hpp @@ -127,6 +127,7 @@ class Microsoft::Console::VirtualTerminal::ITermDispatch virtual void DeviceAttributes() = 0; // DA1 virtual void SecondaryDeviceAttributes() = 0; // DA2 virtual void TertiaryDeviceAttributes() = 0; // DA3 + virtual void RequestTerminalNameVersion() = 0; // XTVERSION virtual void Vt52DeviceAttributes() = 0; // VT52 Identify virtual void RequestTerminalParameters(const DispatchTypes::ReportingPermission permission) = 0; // DECREQTPARM diff --git a/src/terminal/adapter/ITerminalApi.hpp b/src/terminal/adapter/ITerminalApi.hpp index cb67bd1f609..dae664ccb6a 100644 --- a/src/terminal/adapter/ITerminalApi.hpp +++ b/src/terminal/adapter/ITerminalApi.hpp @@ -92,5 +92,7 @@ namespace Microsoft::Console::VirtualTerminal virtual void InvokeCompletions(std::wstring_view menuJson, unsigned int replaceLength) = 0; virtual void SearchMissingCommand(const std::wstring_view command) = 0; + + virtual std::wstring_view GetHostIdentity() const = 0; }; } diff --git a/src/terminal/adapter/adaptDispatch.cpp b/src/terminal/adapter/adaptDispatch.cpp index 633156c51d3..c02209a29fe 100644 --- a/src/terminal/adapter/adaptDispatch.cpp +++ b/src/terminal/adapter/adaptDispatch.cpp @@ -1483,6 +1483,17 @@ void AdaptDispatch::TertiaryDeviceAttributes() _ReturnDcsResponse(L"!|00000000"); } +// Routine Description: +// - XTVERSION - Reports the name and version of the host application. +// The response format is DCS > | text ST. The host identity is determined +// by the host implementation via ITerminalApi::GetHostIdentity(). +// Arguments: +// - +void AdaptDispatch::RequestTerminalNameVersion() +{ + _ReturnDcsResponse(fmt::format(FMT_COMPILE(L">|{}"), _api.GetHostIdentity())); +} + // Routine Description: // - VT52 Identify - Reports the identity of the terminal in VT52 emulation mode. // An actual VT52 terminal would typically identify itself with ESC / K. diff --git a/src/terminal/adapter/adaptDispatch.hpp b/src/terminal/adapter/adaptDispatch.hpp index 15e94867bfa..2ccfbb55c87 100644 --- a/src/terminal/adapter/adaptDispatch.hpp +++ b/src/terminal/adapter/adaptDispatch.hpp @@ -79,6 +79,7 @@ namespace Microsoft::Console::VirtualTerminal void DeviceAttributes() override; // DA1 void SecondaryDeviceAttributes() override; // DA2 void TertiaryDeviceAttributes() override; // DA3 + void RequestTerminalNameVersion() override; // XTVERSION void Vt52DeviceAttributes() override; // VT52 Identify void RequestTerminalParameters(const DispatchTypes::ReportingPermission permission) override; // DECREQTPARM void ScrollUp(const VTInt distance) override; // SU diff --git a/src/terminal/adapter/termDispatch.hpp b/src/terminal/adapter/termDispatch.hpp index 462740a02b1..626089a3080 100644 --- a/src/terminal/adapter/termDispatch.hpp +++ b/src/terminal/adapter/termDispatch.hpp @@ -114,6 +114,7 @@ class Microsoft::Console::VirtualTerminal::TermDispatch : public Microsoft::Cons void DeviceAttributes() override {} // DA1 void SecondaryDeviceAttributes() override {} // DA2 void TertiaryDeviceAttributes() override {} // DA3 + void RequestTerminalNameVersion() override {} // XTVERSION void Vt52DeviceAttributes() override {} // VT52 Identify void RequestTerminalParameters(const DispatchTypes::ReportingPermission /*permission*/) override {} // DECREQTPARM diff --git a/src/terminal/adapter/ut_adapter/adapterTest.cpp b/src/terminal/adapter/ut_adapter/adapterTest.cpp index 596c9ed1a26..4e53269afbc 100644 --- a/src/terminal/adapter/ut_adapter/adapterTest.cpp +++ b/src/terminal/adapter/ut_adapter/adapterTest.cpp @@ -233,6 +233,11 @@ class TestGetSet final : public ITerminalApi Log::Comment(L"SearchMissingCommand MOCK called..."); } + std::wstring_view GetHostIdentity() const override + { + return _hostIdentity; + } + void PrepData() { PrepData(CursorDirection::UP); // if called like this, the cursor direction doesn't matter. @@ -402,6 +407,8 @@ class TestGetSet final : public ITerminalApi std::wstring _expectedMenuJson{}; unsigned int _expectedReplaceLength = 0; + std::wstring_view _hostIdentity{ L"Windows Console Host" }; + private: HANDLE _hCon; }; @@ -1796,6 +1803,23 @@ class AdapterTest VERIFY_THROWS(_pDispatch->TertiaryDeviceAttributes(), std::exception); } + TEST_METHOD(RequestTerminalNameVersionTests) + { + Log::Comment(L"Starting test..."); + + // The test runner is unpackaged, so no package version is available. + Log::Comment(L"Test 1: Verify normal response."); + _testGetSet->PrepData(); + _pDispatch->RequestTerminalNameVersion(); + _testGetSet->ValidateInputEvent(L"\x1bP>|Windows Console Host\x1b\\"); + + Log::Comment(L"Test 2: Verify failure when ReturnResponse doesn't work."); + _testGetSet->PrepData(); + _testGetSet->_returnResponseResult = FALSE; + + VERIFY_THROWS(_pDispatch->RequestTerminalNameVersion(), std::exception); + } + TEST_METHOD(RequestDisplayedExtentTests) { Log::Comment(L"Starting test..."); @@ -4010,6 +4034,9 @@ class AdapterTest _pDispatch->TertiaryDeviceAttributes(); _testGetSet->ValidateInputEvent(L"\x90!|00000000\x9c"); + _pDispatch->RequestTerminalNameVersion(); + _testGetSet->ValidateInputEvent(L"\x90>|Windows Console Host\x9c"); + _pDispatch->RequestColorTableEntry(0); _testGetSet->ValidateInputEvent(L"\x9d\x34;0;rgb:0c0c/0c0c/0c0c\x9c"); @@ -4022,6 +4049,9 @@ class AdapterTest _pDispatch->TertiaryDeviceAttributes(); _testGetSet->ValidateInputEvent(L"\x1bP!|00000000\x1b\\"); + _pDispatch->RequestTerminalNameVersion(); + _testGetSet->ValidateInputEvent(L"\x1bP>|Windows Console Host\x1b\\"); + _pDispatch->RequestColorTableEntry(0); _testGetSet->ValidateInputEvent(L"\x1b]4;0;rgb:0c0c/0c0c/0c0c\x1b\\"); } diff --git a/src/terminal/parser/OutputStateMachineEngine.cpp b/src/terminal/parser/OutputStateMachineEngine.cpp index 5ba404adb49..13c76c1eddf 100644 --- a/src/terminal/parser/OutputStateMachineEngine.cpp +++ b/src/terminal/parser/OutputStateMachineEngine.cpp @@ -533,6 +533,12 @@ bool OutputStateMachineEngine::ActionCsiDispatch(const VTID id, const VTParamete _dispatch->TertiaryDeviceAttributes(); } break; + case CsiActionCodes::XT_RequestVersion: + if (parameters.at(0).value_or(0) == 0) + { + _dispatch->RequestTerminalNameVersion(); + } + break; case CsiActionCodes::DECREQTPARM_RequestTerminalParameters: _dispatch->RequestTerminalParameters(parameters.at(0)); break; diff --git a/src/terminal/parser/OutputStateMachineEngine.hpp b/src/terminal/parser/OutputStateMachineEngine.hpp index be6d054c5a4..3e19dcf3862 100644 --- a/src/terminal/parser/OutputStateMachineEngine.hpp +++ b/src/terminal/parser/OutputStateMachineEngine.hpp @@ -133,6 +133,7 @@ namespace Microsoft::Console::VirtualTerminal SGR_SetGraphicsRendition = VTID("m"), DSR_DeviceStatusReport = VTID("n"), DSR_PrivateDeviceStatusReport = VTID("?n"), + XT_RequestVersion = VTID(">q"), DECSTBM_SetTopBottomMargins = VTID("r"), DECSLRM_SetLeftRightMargins = VTID("s"), DTTERM_WindowManipulation = VTID("t"), // NOTE: Overlaps with DECSLPP. Fix when/if implemented. diff --git a/src/terminal/parser/ut_parser/OutputEngineTest.cpp b/src/terminal/parser/ut_parser/OutputEngineTest.cpp index f4db87490ff..3618b5890bf 100644 --- a/src/terminal/parser/ut_parser/OutputEngineTest.cpp +++ b/src/terminal/parser/ut_parser/OutputEngineTest.cpp @@ -1142,6 +1142,7 @@ class StatefulDispatch final : public TermDispatch _deviceAttributes{ false }, _secondaryDeviceAttributes{ false }, _tertiaryDeviceAttributes{ false }, + _requestTerminalNameVersion{ false }, _vt52DeviceAttributes{ false }, _requestTerminalParameters{ false }, _reportingPermission{ (DispatchTypes::ReportingPermission)-1 }, @@ -1308,6 +1309,11 @@ class StatefulDispatch final : public TermDispatch _tertiaryDeviceAttributes = true; } + void RequestTerminalNameVersion() noexcept override + { + _requestTerminalNameVersion = true; + } + void Vt52DeviceAttributes() noexcept override { _vt52DeviceAttributes = true; @@ -1472,6 +1478,7 @@ class StatefulDispatch final : public TermDispatch bool _deviceAttributes; bool _secondaryDeviceAttributes; bool _tertiaryDeviceAttributes; + bool _requestTerminalNameVersion; bool _vt52DeviceAttributes; bool _requestTerminalParameters; DispatchTypes::ReportingPermission _reportingPermission; @@ -2392,6 +2399,46 @@ class StateMachineExternalTest final pDispatch->ClearState(); } + TEST_METHOD(TestRequestTerminalNameVersion) + { + auto dispatch = std::make_unique(); + auto pDispatch = dispatch.get(); + auto engine = std::make_unique(std::move(dispatch)); + StateMachine mach(std::move(engine)); + + Log::Comment(L"Test 1: Check default case, no params."); + mach.ProcessCharacter(AsciiChars::ESC); + mach.ProcessCharacter(L'['); + mach.ProcessCharacter(L'>'); + mach.ProcessCharacter(L'q'); + + VERIFY_IS_TRUE(pDispatch->_requestTerminalNameVersion); + + pDispatch->ClearState(); + + Log::Comment(L"Test 2: Check default case, 0 param."); + mach.ProcessCharacter(AsciiChars::ESC); + mach.ProcessCharacter(L'['); + mach.ProcessCharacter(L'>'); + mach.ProcessCharacter(L'0'); + mach.ProcessCharacter(L'q'); + + VERIFY_IS_TRUE(pDispatch->_requestTerminalNameVersion); + + pDispatch->ClearState(); + + Log::Comment(L"Test 3: Check fail case, 1 (or any other) param."); + mach.ProcessCharacter(AsciiChars::ESC); + mach.ProcessCharacter(L'['); + mach.ProcessCharacter(L'>'); + mach.ProcessCharacter(L'1'); + mach.ProcessCharacter(L'q'); + + VERIFY_IS_FALSE(pDispatch->_requestTerminalNameVersion); + + pDispatch->ClearState(); + } + TEST_METHOD(TestRequestTerminalParameters) { auto dispatch = std::make_unique();