This commit is contained in:
Leonard Hecker
2026-02-03 00:07:02 +01:00
parent cb88820fa6
commit ad501c9d92
14 changed files with 1870 additions and 2391 deletions

208
reference.cpp Normal file
View File

@@ -0,0 +1,208 @@
void TerminalInput::_initKeyboardMap() noexcept
try
{
auto defineKeyWithUnusedModifiers = [this](const int keyCode, const std::wstring& sequence) {
for (auto m = 0; m < 8; m++)
_keyMap[VTModifier(m) + keyCode] = sequence;
};
auto defineKeyWithAltModifier = [this](const int keyCode, const std::wstring& sequence) {
_keyMap[keyCode] = sequence;
_keyMap[Alt + keyCode] = L"\x1B" + sequence;
};
auto defineKeypadKey = [this](const int keyCode, const wchar_t* prefix, const wchar_t finalChar) {
_keyMap[keyCode] = fmt::format(FMT_COMPILE(L"{}{}"), prefix, finalChar);
for (auto m = 1; m < 8; m++)
_keyMap[VTModifier(m) + keyCode] = fmt::format(FMT_COMPILE(L"{}1;{}{}"), _csi, m + 1, finalChar);
};
auto defineEditingKey = [this](const int keyCode, const int parm) {
_keyMap[keyCode] = fmt::format(FMT_COMPILE(L"{}{}~"), _csi, parm);
for (auto m = 1; m < 8; m++)
_keyMap[VTModifier(m) + keyCode] = fmt::format(FMT_COMPILE(L"{}{};{}~"), _csi, parm, m + 1);
};
auto defineNumericKey = [this](const int keyCode, const wchar_t finalChar) {
_keyMap[keyCode] = fmt::format(FMT_COMPILE(L"{}{}"), _ss3, finalChar);
for (auto m = 1; m < 8; m++)
_keyMap[VTModifier(m) + keyCode] = fmt::format(FMT_COMPILE(L"{}{}{}"), _ss3, m + 1, finalChar);
};
_keyMap.clear();
// The CSI and SS3 introducers are C1 control codes, which can either be
// sent as a single codepoint, or as a two character escape sequence.
if (_inputMode.test(Mode::SendC1))
{
_csi = L"\x9B";
_ss3 = L"\x8F";
}
else
{
_csi = L"\x1B[";
_ss3 = L"\x1BO";
}
// PAUSE doesn't have a VT mapping, but traditionally we've mapped it to ^Z,
// regardless of modifiers.
defineKeyWithUnusedModifiers(VK_PAUSE, L"\x1A"s);
// BACKSPACE maps to either DEL or BS, depending on the Backarrow Key mode.
// The Ctrl modifier inverts the active mode, swapping BS and DEL (this is
// not standard, but a modern terminal convention). The Alt modifier adds
// an ESC prefix (also not standard).
const auto backSequence = _inputMode.test(Mode::BackarrowKey) ? L"\b"s : L"\x7F"s;
const auto ctrlBackSequence = _inputMode.test(Mode::BackarrowKey) ? L"\x7F"s : L"\b"s;
defineKeyWithAltModifier(VK_BACK, backSequence);
defineKeyWithAltModifier(Ctrl + VK_BACK, ctrlBackSequence);
defineKeyWithAltModifier(Shift + VK_BACK, backSequence);
defineKeyWithAltModifier(Ctrl + Shift + VK_BACK, ctrlBackSequence);
// TAB maps to HT, and Shift+TAB to CBT. The Ctrl modifier has no effect.
// The Alt modifier adds an ESC prefix, although in practice all the Alt
// mappings are likely to be system hotkeys.
const auto shiftTabSequence = fmt::format(FMT_COMPILE(L"{}Z"), _csi);
defineKeyWithAltModifier(VK_TAB, L"\t"s);
defineKeyWithAltModifier(Ctrl + VK_TAB, L"\t"s);
defineKeyWithAltModifier(Shift + VK_TAB, shiftTabSequence);
defineKeyWithAltModifier(Ctrl + Shift + VK_TAB, shiftTabSequence);
// RETURN maps to either CR or CR LF, depending on the Line Feed mode. With
// a Ctrl modifier it maps to LF, because that's the expected behavior for
// most PC keyboard layouts. The Alt modifier adds an ESC prefix.
const auto returnSequence = _inputMode.test(Mode::LineFeed) ? L"\r\n"s : L"\r"s;
defineKeyWithAltModifier(VK_RETURN, returnSequence);
defineKeyWithAltModifier(Shift + VK_RETURN, returnSequence);
defineKeyWithAltModifier(Ctrl + VK_RETURN, L"\n"s);
defineKeyWithAltModifier(Ctrl + Shift + VK_RETURN, L"\n"s);
// The keypad RETURN key works the same way, except when Keypad mode is
// enabled, but that's handled below with the other keypad keys.
defineKeyWithAltModifier(Enhanced + VK_RETURN, returnSequence);
defineKeyWithAltModifier(Shift + Enhanced + VK_RETURN, returnSequence);
defineKeyWithAltModifier(Ctrl + Enhanced + VK_RETURN, L"\n"s);
defineKeyWithAltModifier(Ctrl + Shift + Enhanced + VK_RETURN, L"\n"s);
if (_inputMode.test(Mode::Ansi))
{
// F1 to F4 map to the VT keypad function keys, which are SS3 sequences.
// When combined with a modifier, we use CSI sequences with the modifier
// embedded as a parameter (not standard - a modern terminal extension).
defineKeypadKey(VK_F1, _ss3, L'P');
defineKeypadKey(VK_F2, _ss3, L'Q');
defineKeypadKey(VK_F3, _ss3, L'R');
defineKeypadKey(VK_F4, _ss3, L'S');
// F5 through F20 map to the top row VT function keys. They use standard
// DECFNK sequences with the modifier embedded as a parameter. The first
// five function keys on a VT terminal are typically local functions, so
// there's not much need to support mappings for them.
for (auto vk = VK_F5; vk <= VK_F20; vk++)
{
static constexpr std::array<uint8_t, 16> parameters = { 15, 17, 18, 19, 20, 21, 23, 24, 25, 26, 28, 29, 31, 32, 33, 34 };
const auto parm = parameters.at(static_cast<size_t>(vk) - VK_F5);
defineEditingKey(vk, parm);
}
// Cursor keys follow a similar pattern to the VT keypad function keys,
// although they only use an SS3 prefix when the Cursor Key mode is set.
// When combined with a modifier, they'll use CSI sequences with the
// modifier embedded as a parameter (again not standard).
const auto ckIntroducer = _inputMode.test(Mode::CursorKey) ? _ss3 : _csi;
defineKeypadKey(VK_UP, ckIntroducer, L'A');
defineKeypadKey(VK_DOWN, ckIntroducer, L'B');
defineKeypadKey(VK_RIGHT, ckIntroducer, L'C');
defineKeypadKey(VK_LEFT, ckIntroducer, L'D');
defineKeypadKey(VK_CLEAR, ckIntroducer, L'E');
defineKeypadKey(VK_HOME, ckIntroducer, L'H');
defineKeypadKey(VK_END, ckIntroducer, L'F');
// Editing keys follow the same pattern as the top row VT function
// keys, using standard DECFNK sequences with the modifier embedded.
defineEditingKey(VK_INSERT, 2);
defineEditingKey(VK_DELETE, 3);
defineEditingKey(VK_PRIOR, 5);
defineEditingKey(VK_NEXT, 6);
// Keypad keys depend on the Keypad mode. When reset, they transmit
// the ASCII character assigned by the keyboard layout, but when set
// they transmit SS3 escape sequences. When used with a modifier, the
// modifier is embedded as a parameter value (not standard).
if (Feature_KeypadModeEnabled::IsEnabled() && _inputMode.test(Mode::Keypad))
{
defineNumericKey(VK_MULTIPLY, L'j');
defineNumericKey(VK_ADD, L'k');
defineNumericKey(VK_SEPARATOR, L'l');
defineNumericKey(VK_SUBTRACT, L'm');
defineNumericKey(VK_DECIMAL, L'n');
defineNumericKey(VK_DIVIDE, L'o');
defineNumericKey(VK_NUMPAD0, L'p');
defineNumericKey(VK_NUMPAD1, L'q');
defineNumericKey(VK_NUMPAD2, L'r');
defineNumericKey(VK_NUMPAD3, L's');
defineNumericKey(VK_NUMPAD4, L't');
defineNumericKey(VK_NUMPAD5, L'u');
defineNumericKey(VK_NUMPAD6, L'v');
defineNumericKey(VK_NUMPAD7, L'w');
defineNumericKey(VK_NUMPAD8, L'x');
defineNumericKey(VK_NUMPAD9, L'y');
defineNumericKey(Enhanced + VK_RETURN, L'M');
}
}
else
{
// In VT52 mode, the sequences tend to use the same final character as
// their ANSI counterparts, but with a simple ESC prefix. The modifier
// keys have no effect.
// VT52 only support PF1 through PF4 function keys.
defineKeyWithUnusedModifiers(VK_F1, L"\033P"s);
defineKeyWithUnusedModifiers(VK_F2, L"\033Q"s);
defineKeyWithUnusedModifiers(VK_F3, L"\033R"s);
defineKeyWithUnusedModifiers(VK_F4, L"\033S"s);
// But terminals with application functions keys would
// map some of them as controls keys in VT52 mode.
defineKeyWithUnusedModifiers(VK_F11, L"\033"s);
defineKeyWithUnusedModifiers(VK_F12, L"\b"s);
defineKeyWithUnusedModifiers(VK_F13, L"\n"s);
// Cursor keys use the same finals as the ANSI sequences.
defineKeyWithUnusedModifiers(VK_UP, L"\033A"s);
defineKeyWithUnusedModifiers(VK_DOWN, L"\033B"s);
defineKeyWithUnusedModifiers(VK_RIGHT, L"\033C"s);
defineKeyWithUnusedModifiers(VK_LEFT, L"\033D"s);
defineKeyWithUnusedModifiers(VK_CLEAR, L"\033E"s);
defineKeyWithUnusedModifiers(VK_HOME, L"\033H"s);
defineKeyWithUnusedModifiers(VK_END, L"\033F"s);
// Keypad keys also depend on Keypad mode, the same as ANSI mappings,
// but the sequences use an ESC ? prefix instead of SS3.
if (Feature_KeypadModeEnabled::IsEnabled() && _inputMode.test(Mode::Keypad))
{
defineKeyWithUnusedModifiers(VK_MULTIPLY, L"\033?j"s);
defineKeyWithUnusedModifiers(VK_ADD, L"\033?k"s);
defineKeyWithUnusedModifiers(VK_SEPARATOR, L"\033?l"s);
defineKeyWithUnusedModifiers(VK_SUBTRACT, L"\033?m"s);
defineKeyWithUnusedModifiers(VK_DECIMAL, L"\033?n"s);
defineKeyWithUnusedModifiers(VK_DIVIDE, L"\033?o"s);
defineKeyWithUnusedModifiers(VK_NUMPAD0, L"\033?p"s);
defineKeyWithUnusedModifiers(VK_NUMPAD1, L"\033?q"s);
defineKeyWithUnusedModifiers(VK_NUMPAD2, L"\033?r"s);
defineKeyWithUnusedModifiers(VK_NUMPAD3, L"\033?s"s);
defineKeyWithUnusedModifiers(VK_NUMPAD4, L"\033?t"s);
defineKeyWithUnusedModifiers(VK_NUMPAD5, L"\033?u"s);
defineKeyWithUnusedModifiers(VK_NUMPAD6, L"\033?v"s);
defineKeyWithUnusedModifiers(VK_NUMPAD7, L"\033?w"s);
defineKeyWithUnusedModifiers(VK_NUMPAD8, L"\033?x"s);
defineKeyWithUnusedModifiers(VK_NUMPAD9, L"\033?y"s);
defineKeyWithUnusedModifiers(Enhanced + VK_RETURN, L"\033?M"s);
}
}
_focusInSequence = _csi + L"I"s;
_focusOutSequence = _csi + L"O"s;
}
CATCH_LOG()

View File

@@ -600,96 +600,12 @@ namespace
},
},
};
#pragma region TAEF hookup for the test case array above
struct ArrayIndexTaefAdapterRow : public Microsoft::WRL::RuntimeClass<Microsoft::WRL::RuntimeClassFlags<Microsoft::WRL::ClassicCom | Microsoft::WRL::InhibitFtmBase>, IDataRow>
{
HRESULT RuntimeClassInitialize(const size_t index)
{
_index = index;
return S_OK;
}
STDMETHODIMP GetTestData(BSTR /*pszName*/, SAFEARRAY** ppData) override
{
const auto indexString{ wil::str_printf<std::wstring>(L"%zu", _index) };
auto safeArray{ SafeArrayCreateVector(VT_BSTR, 0, 1) };
LONG index{ 0 };
auto indexBstr{ wil::make_bstr(indexString.c_str()) };
(void)SafeArrayPutElement(safeArray, &index, indexBstr.release());
*ppData = safeArray;
return S_OK;
}
STDMETHODIMP GetMetadataNames(SAFEARRAY** ppMetadataNames) override
{
*ppMetadataNames = nullptr;
return S_FALSE;
}
STDMETHODIMP GetMetadata(BSTR /*pszName*/, SAFEARRAY** ppData) override
{
*ppData = nullptr;
return S_FALSE;
}
STDMETHODIMP GetName(BSTR* ppszRowName) override
{
*ppszRowName = nullptr;
return S_FALSE;
}
private:
size_t _index;
};
struct ArrayIndexTaefAdapterSource : public Microsoft::WRL::RuntimeClass<Microsoft::WRL::RuntimeClassFlags<Microsoft::WRL::ClassicCom | Microsoft::WRL::InhibitFtmBase>, IDataSource>
{
STDMETHODIMP Advance(IDataRow** ppDataRow) override
{
if (_index < std::extent_v<decltype(testCases)>)
{
Microsoft::WRL::MakeAndInitialize<ArrayIndexTaefAdapterRow>(ppDataRow, _index++);
}
else
{
*ppDataRow = nullptr;
}
return S_OK;
}
STDMETHODIMP Reset() override
{
_index = 0;
return S_OK;
}
STDMETHODIMP GetTestDataNames(SAFEARRAY** names) override
{
auto safeArray{ SafeArrayCreateVector(VT_BSTR, 0, 1) };
LONG index{ 0 };
auto dataNameBstr{ wil::make_bstr(L"index") };
(void)SafeArrayPutElement(safeArray, &index, dataNameBstr.release());
*names = safeArray;
return S_OK;
}
STDMETHODIMP GetTestDataType(BSTR /*name*/, BSTR* type) override
{
*type = nullptr;
return S_OK;
}
private:
size_t _index{ 0 };
};
#pragma endregion
}
extern "C" HRESULT __declspec(dllexport) __cdecl ReflowTestDataSource(IDataSource** ppDataSource, void*)
{
auto source{ Microsoft::WRL::Make<ArrayIndexTaefAdapterSource>() };
return source.CopyTo(ppDataSource);
*ppDataSource = new ArrayIndexTaefAdapterSource>(std::size(testCases));
return S_OK;
}
class ReflowTests

View File

@@ -19,6 +19,8 @@ Revision History:
#include <til/bit.h>
#include <IDataSource.h>
// Helper for declaring a variable to store a TEST_METHOD_PROPERTY and get it's value from the test metadata
#define INIT_TEST_PROPERTY(type, identifier, description) \
type identifier; \
@@ -45,6 +47,178 @@ Revision History:
namespace WEX::TestExecution
{
struct ArrayIndexTaefAdapterRow : IDataRow
{
ArrayIndexTaefAdapterRow(size_t index) :
_index(index) {}
// IUnknown
STDMETHODIMP QueryInterface(REFIID riid, void** ppvObject) override
{
if (!ppvObject)
{
return E_POINTER;
}
if (riid == __uuidof(IUnknown))
{
AddRef();
*ppvObject = static_cast<IUnknown*>(this);
return S_OK;
}
else if (riid == __uuidof(IDataRow))
{
*ppvObject = static_cast<IDataRow*>(this);
AddRef();
return S_OK;
}
else
{
*ppvObject = nullptr;
return E_NOINTERFACE;
}
}
ULONG STDMETHODCALLTYPE AddRef() override
{
return _refCount.fetch_add(1) + 1;
}
ULONG STDMETHODCALLTYPE Release() override
{
const auto count = _refCount.fetch_sub(1) - 1;
if (count == 0)
{
delete this;
}
return count;
}
// IDataRow
STDMETHODIMP GetTestData(BSTR /*pszName*/, SAFEARRAY** ppData) override
{
wchar_t buf[16];
swprintf_s(buf, L"%zu", _index);
LONG idx = 0;
const auto array = SafeArrayCreateVector(VT_BSTR, 0, 1);
SafeArrayPutElement(array, &idx, SysAllocString(buf));
*ppData = array;
return S_OK;
}
STDMETHODIMP GetMetadataNames(SAFEARRAY** ppMetadataNames) override
{
*ppMetadataNames = nullptr;
return S_FALSE;
}
STDMETHODIMP GetMetadata(BSTR /*pszName*/, SAFEARRAY** ppData) override
{
*ppData = nullptr;
return S_FALSE;
}
STDMETHODIMP GetName(BSTR* ppszRowName) override
{
*ppszRowName = nullptr;
return S_FALSE;
}
private:
std::atomic<ULONG> _refCount{ 1 };
size_t _index = 0;
};
struct ArrayIndexTaefAdapterSource : IDataSource
{
explicit ArrayIndexTaefAdapterSource(size_t count) :
_count{ count } {}
// IUnknown
STDMETHODIMP QueryInterface(REFIID riid, void** ppvObject) override
{
if (!ppvObject)
{
return E_POINTER;
}
if (riid == __uuidof(IUnknown))
{
AddRef();
*ppvObject = static_cast<IUnknown*>(this);
return S_OK;
}
else if (riid == __uuidof(IDataSource))
{
*ppvObject = static_cast<IDataSource*>(this);
AddRef();
return S_OK;
}
else
{
*ppvObject = nullptr;
return E_NOINTERFACE;
}
}
ULONG STDMETHODCALLTYPE AddRef() override
{
return _refCount.fetch_add(1) + 1;
}
ULONG STDMETHODCALLTYPE Release() override
{
const auto count = _refCount.fetch_sub(1) - 1;
if (count == 0)
{
delete this;
}
return count;
}
// IDataSource
STDMETHODIMP Advance(IDataRow** ppDataRow) override
{
if (_index < _count)
{
*ppDataRow = static_cast<IDataRow*>(new ArrayIndexTaefAdapterRow(_index++));
return S_OK;
}
else
{
*ppDataRow = nullptr;
}
return S_OK;
}
STDMETHODIMP Reset() override
{
_index = 0;
return S_OK;
}
STDMETHODIMP GetTestDataNames(SAFEARRAY** names) override
{
LONG idx = 0;
const auto array = SafeArrayCreateVector(VT_BSTR, 0, 1);
SafeArrayPutElement(array, &idx, SysAllocString(L"index"));
*names = array;
return S_OK;
}
STDMETHODIMP GetTestDataType(BSTR /*name*/, BSTR* type) override
{
*type = nullptr;
return S_OK;
}
private:
std::atomic<ULONG> _refCount{ 1 };
size_t _count = 0;
size_t _index = 0;
};
// Compare two floats using a ULP (unit last place) tolerance of up to 4.
// Allows you to compare two floats that are almost equal.
// Think of: 0.200000000000000 vs. 0.200000000000001.

View File

@@ -109,97 +109,10 @@ static constexpr til::point point_offset_by_line(const til::point start, const t
// IMPORTANT: reference this _after_ defining point_offset_by_XXX. We need it for some definitions
#include "GeneratedUiaTextRangeMovementTests.g.cpp"
namespace
{
#pragma region TAEF hookup for the test case array above
struct ArrayIndexTaefAdapterRow : public Microsoft::WRL::RuntimeClass<Microsoft::WRL::RuntimeClassFlags<Microsoft::WRL::ClassicCom | Microsoft::WRL::InhibitFtmBase>, IDataRow>
{
HRESULT RuntimeClassInitialize(const size_t index)
{
_index = index;
return S_OK;
}
STDMETHODIMP GetTestData(BSTR /*pszName*/, SAFEARRAY** ppData) override
{
const auto indexString{ wil::str_printf<std::wstring>(L"%zu", _index) };
auto safeArray{ SafeArrayCreateVector(VT_BSTR, 0, 1) };
LONG index{ 0 };
auto indexBstr{ wil::make_bstr(indexString.c_str()) };
(void)SafeArrayPutElement(safeArray, &index, indexBstr.release());
*ppData = safeArray;
return S_OK;
}
STDMETHODIMP GetMetadataNames(SAFEARRAY** ppMetadataNames) override
{
*ppMetadataNames = nullptr;
return S_FALSE;
}
STDMETHODIMP GetMetadata(BSTR /*pszName*/, SAFEARRAY** ppData) override
{
*ppData = nullptr;
return S_FALSE;
}
STDMETHODIMP GetName(BSTR* ppszRowName) override
{
*ppszRowName = wil::make_bstr(s_movementTests[_index].name.data()).release();
return S_OK;
}
private:
size_t _index;
};
struct ArrayIndexTaefAdapterSource : public Microsoft::WRL::RuntimeClass<Microsoft::WRL::RuntimeClassFlags<Microsoft::WRL::ClassicCom | Microsoft::WRL::InhibitFtmBase>, IDataSource>
{
STDMETHODIMP Advance(IDataRow** ppDataRow) override
{
if (_index < s_movementTests.size())
{
Microsoft::WRL::MakeAndInitialize<ArrayIndexTaefAdapterRow>(ppDataRow, _index++);
}
else
{
*ppDataRow = nullptr;
}
return S_OK;
}
STDMETHODIMP Reset() override
{
_index = 0;
return S_OK;
}
STDMETHODIMP GetTestDataNames(SAFEARRAY** names) override
{
auto safeArray{ SafeArrayCreateVector(VT_BSTR, 0, 1) };
LONG index{ 0 };
auto dataNameBstr{ wil::make_bstr(L"index") };
(void)SafeArrayPutElement(safeArray, &index, dataNameBstr.release());
*names = safeArray;
return S_OK;
}
STDMETHODIMP GetTestDataType(BSTR /*name*/, BSTR* type) override
{
*type = nullptr;
return S_OK;
}
private:
size_t _index{ 0 };
};
#pragma endregion
}
extern "C" HRESULT __declspec(dllexport) __cdecl GeneratedMovementTestDataSource(IDataSource** ppDataSource, void*)
{
auto source{ Microsoft::WRL::Make<ArrayIndexTaefAdapterSource>() };
return source.CopyTo(ppDataSource);
*ppDataSource = new ArrayIndexTaefAdapterSource>(std::size(testCases));
return S_OK;
}
// UiaTextRange takes an object that implements

View File

@@ -67,7 +67,7 @@ public:
virtual void DeleteColumn(const VTInt distance) = 0; // DECDC
virtual void SetKeypadMode(const bool applicationMode) = 0; // DECKPAM, DECKPNM
virtual void SetAnsiMode(const bool ansiMode) = 0; // DECANM
virtual void SetKittyKeyboardProtocol(const VTParameter flags, const VTParameter mode) = 0; // CSI = flags ; mode u
virtual void SetKittyKeyboardProtocol(const VTParameter flags, const VTParameter mode) noexcept = 0; // CSI = flags ; mode u
virtual void QueryKittyKeyboardProtocol() = 0; // CSI ? u
virtual void PushKittyKeyboardProtocol(const VTParameter flags) = 0; // CSI > flags u
virtual void PopKittyKeyboardProtocol(const VTParameter count) = 0; // CSI < count u

View File

@@ -2055,9 +2055,9 @@ void AdaptDispatch::SetKeypadMode(const bool fApplicationMode) noexcept
}
// CSI = flags ; mode u - Sets kitty keyboard protocol flags
void AdaptDispatch::SetKittyKeyboardProtocol(const VTParameter flags, const VTParameter mode)
void AdaptDispatch::SetKittyKeyboardProtocol(const VTParameter flags, const VTParameter mode) noexcept
{
const auto kittyFlags = static_cast<uint8_t>(flags.value_or(0));
const auto kittyFlags = gsl::narrow_cast<uint8_t>(flags.value_or(0));
const auto KittyKeyboardProtocol = static_cast<TerminalInput::KittyKeyboardProtocolMode>(mode.value_or(1));
_terminalInput.SetKittyKeyboardProtocol(kittyFlags, KittyKeyboardProtocol);
}
@@ -2072,7 +2072,7 @@ void AdaptDispatch::QueryKittyKeyboardProtocol()
// CSI > flags u - Pushes current kitty keyboard flags onto the stack and sets new flags
void AdaptDispatch::PushKittyKeyboardProtocol(const VTParameter flags)
{
const auto kittyFlags = static_cast<uint8_t>(flags.value_or(0));
const auto kittyFlags = gsl::narrow_cast<uint8_t>(flags.value_or(0));
_terminalInput.PushKittyFlags(kittyFlags);
}

View File

@@ -97,7 +97,7 @@ namespace Microsoft::Console::VirtualTerminal
void RequestMode(const DispatchTypes::ModeParams param) override; // DECRQM
void SetKeypadMode(const bool applicationMode) noexcept override; // DECKPAM, DECKPNM
void SetAnsiMode(const bool ansiMode) override; // DECANM
void SetKittyKeyboardProtocol(const VTParameter flags, const VTParameter mode) override; // Kitty keyboard protocol CSI = flags ; mode u
void SetKittyKeyboardProtocol(const VTParameter flags, const VTParameter mode) noexcept override; // Kitty keyboard protocol CSI = flags ; mode u
void QueryKittyKeyboardProtocol() override; // Kitty keyboard protocol CSI ? u
void PushKittyKeyboardProtocol(const VTParameter flags) override; // Kitty keyboard protocol CSI > flags u
void PopKittyKeyboardProtocol(const VTParameter count) override; // Kitty keyboard protocol CSI < count u

View File

@@ -54,7 +54,7 @@ public:
void DeleteColumn(const VTInt /*distance*/) override {} // DECDC
void SetKeypadMode(const bool /*applicationMode*/) override {} // DECKPAM, DECKPNM
void SetAnsiMode(const bool /*ansiMode*/) override {} // DECANM
void SetKittyKeyboardProtocol(const VTParameter /*flags*/, const VTParameter /*mode*/) override {} // CSI = flags ; mode u
void SetKittyKeyboardProtocol(const VTParameter /*flags*/, const VTParameter /*mode*/) noexcept override {} // CSI = flags ; mode u
void QueryKittyKeyboardProtocol() override {} // CSI ? u
void PushKittyKeyboardProtocol(const VTParameter /*flags*/) override {} // CSI > flags u
void PopKittyKeyboardProtocol(const VTParameter /*count*/) override {} // CSI < count u

View File

@@ -13,6 +13,7 @@
<ItemGroup>
<ClCompile Include="adapterTest.cpp" />
<ClCompile Include="inputTest.cpp" />
<ClCompile Include="kittyKeyboardProtocol.cpp" />
<ClCompile Include="MouseInputTest.cpp" />
<ClCompile Include="..\precomp.cpp">
<PrecompiledHeader>Create</PrecompiledHeader>
@@ -71,4 +72,4 @@
<Import Project="$(SolutionDir)src\common.build.post.props" />
<Import Project="$(SolutionDir)src\common.build.tests.props" />
<Import Project="$(SolutionDir)src\common.nugetversions.targets" />
</Project>
</Project>

View File

@@ -27,10 +27,18 @@
<ClCompile Include="MouseInputTest.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="kittyKeyboardProtocol.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\precomp.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<Natvis Include="$(SolutionDir)tools\ConsoleTypes.natvis" />
<Natvis Include="$(MSBuildThisFileDirectory)..\..\natvis\wil.natvis" />
<Natvis Include="$(MSBuildThisFileDirectory)..\..\natvis\wil.natstepfilter" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,720 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "precomp.h"
#include <WexTestClass.h>
#include <consoletaeftemplates.hpp>
#include "../../input/terminalInput.hpp"
using namespace WEX::TestExecution;
using namespace WEX::Logging;
using namespace WEX::Common;
using namespace Microsoft::Console::VirtualTerminal;
using KittyKeyboardProtocolFlags = TerminalInput::KittyKeyboardProtocolFlags;
using KittyKeyboardProtocolMode = TerminalInput::KittyKeyboardProtocolMode;
namespace
{
TerminalInput::OutputType process(TerminalInput& input, bool keyDown, uint16_t vk, uint16_t sc, wchar_t ch, uint32_t state)
{
INPUT_RECORD record{};
record.EventType = KEY_EVENT;
record.Event.KeyEvent.bKeyDown = keyDown ? TRUE : FALSE;
record.Event.KeyEvent.wRepeatCount = 1;
record.Event.KeyEvent.wVirtualKeyCode = vk;
record.Event.KeyEvent.wVirtualScanCode = sc;
record.Event.KeyEvent.uChar.UnicodeChar = ch;
record.Event.KeyEvent.dwControlKeyState = state;
return input.HandleKey(record);
}
TerminalInput createInput(uint8_t flags)
{
TerminalInput input;
input.SetKittyKeyboardProtocol(flags, KittyKeyboardProtocolMode::Replace);
return input;
}
// Kitty modifier bit values (as transmitted, before adding 1):
// shift=1, alt=2, ctrl=4, super=8, hyper=16, meta=32, caps_lock=64, num_lock=128
// Transmitted as: 1 + modifiers
// CSI = "\x1b["
// Helper macros for common state combinations
constexpr auto ALT_PRESSED = LEFT_ALT_PRESSED; // Use left for consistency
constexpr auto CTRL_PRESSED = LEFT_CTRL_PRESSED;
struct TestCase
{
std::wstring_view name;
std::wstring_view expected;
uint8_t flags; // KittyKeyboardProtocolFlags
bool keyDown;
uint16_t vk;
uint16_t sc;
wchar_t ch;
uint32_t state;
};
// ========================================================================
// Test case organization:
//
// 1. FLAG COMBINATIONS (32 total = 2^5 enhancement flags)
// Testing each flag combination with a representative key
//
// 2. MODIFIER COMBINATIONS
// Testing all modifier permutations (shift, alt, ctrl, caps_lock, num_lock)
//
// 3. SPECIAL KEY BEHAVIORS
// - Enter/Tab/Backspace legacy behavior
// - Escape key disambiguation
// - Keypad keys
// - Function keys
// - Lock keys
// - Modifier keys themselves
//
// 4. EVENT TYPES
// - Press, repeat, release events
// - Special handling for Enter/Tab/Backspace release
//
// 5. ALTERNATE KEYS
// - Shifted key codes
// - Base layout key codes
//
// 6. TEXT AS CODEPOINTS
// - Text embedded in escape codes
// ========================================================================
constexpr TestCase testCases[] = {
// ====================================================================
// SECTION 1: Enhancement Flag Combinations (32 combinations)
// Using Escape key as representative since it's affected by Disambiguate
// ====================================================================
// flags=0 (0b00000): No enhancements - legacy mode
// Escape key in legacy mode: just ESC byte
TestCase{ L"Flags=0 (none) Esc key", L"\x1b", 0, true, VK_ESCAPE, 0x01, 0, 0 },
// flags=1 (0b00001): DisambiguateEscapeCodes only
// Escape key becomes CSI 27 u
TestCase{ L"Flags=1 (Disambiguate) Esc key", L"\x1b[27u", 1, true, VK_ESCAPE, 0x01, 0, 0 },
// flags=2 (0b00010): ReportEventTypes only
// No disambiguation, so Esc is still legacy (but with event type tracking internally)
TestCase{ L"Flags=2 (EventTypes) Esc key down", L"\x1b", 2, true, VK_ESCAPE, 0x01, 0, 0 },
// flags=3 (0b00011): Disambiguate + EventTypes
// Escape key with event type: CSI 27;1:1 u (mod=1, event=press=1)
TestCase{ L"Flags=3 (Disambiguate+EventTypes) Esc key press", L"\x1b[27;1:1u", 3, true, VK_ESCAPE, 0x01, 0, 0 },
// flags=4 (0b00100): ReportAlternateKeys only
// Without Disambiguate, Escape is still legacy
TestCase{ L"Flags=4 (AltKeys) Esc key", L"\x1b", 4, true, VK_ESCAPE, 0x01, 0, 0 },
// flags=5 (0b00101): Disambiguate + AltKeys
TestCase{ L"Flags=5 (Disambiguate+AltKeys) Esc key", L"\x1b[27u", 5, true, VK_ESCAPE, 0x01, 0, 0 },
// flags=6 (0b00110): EventTypes + AltKeys
TestCase{ L"Flags=6 (EventTypes+AltKeys) Esc key", L"\x1b", 6, true, VK_ESCAPE, 0x01, 0, 0 },
// flags=7 (0b00111): Disambiguate + EventTypes + AltKeys
TestCase{ L"Flags=7 (Disambiguate+EventTypes+AltKeys) Esc key press", L"\x1b[27;1:1u", 7, true, VK_ESCAPE, 0x01, 0, 0 },
// flags=8 (0b01000): ReportAllKeysAsEscapeCodes only
// All keys become CSI u, including Escape
TestCase{ L"Flags=8 (AllKeys) Esc key", L"\x1b[27u", 8, true, VK_ESCAPE, 0x01, 0, 0 },
// flags=9 (0b01001): Disambiguate + AllKeys
TestCase{ L"Flags=9 (Disambiguate+AllKeys) Esc key", L"\x1b[27u", 9, true, VK_ESCAPE, 0x01, 0, 0 },
// flags=10 (0b01010): EventTypes + AllKeys
TestCase{ L"Flags=10 (EventTypes+AllKeys) Esc key press", L"\x1b[27;1:1u", 10, true, VK_ESCAPE, 0x01, 0, 0 },
// flags=11 (0b01011): Disambiguate + EventTypes + AllKeys
TestCase{ L"Flags=11 (Disambiguate+EventTypes+AllKeys) Esc key press", L"\x1b[27;1:1u", 11, true, VK_ESCAPE, 0x01, 0, 0 },
// flags=12 (0b01100): AltKeys + AllKeys
TestCase{ L"Flags=12 (AltKeys+AllKeys) Esc key", L"\x1b[27u", 12, true, VK_ESCAPE, 0x01, 0, 0 },
// flags=13 (0b01101): Disambiguate + AltKeys + AllKeys
TestCase{ L"Flags=13 (Disambiguate+AltKeys+AllKeys) Esc key", L"\x1b[27u", 13, true, VK_ESCAPE, 0x01, 0, 0 },
// flags=14 (0b01110): EventTypes + AltKeys + AllKeys
TestCase{ L"Flags=14 (EventTypes+AltKeys+AllKeys) Esc key press", L"\x1b[27;1:1u", 14, true, VK_ESCAPE, 0x01, 0, 0 },
// flags=15 (0b01111): Disambiguate + EventTypes + AltKeys + AllKeys
TestCase{ L"Flags=15 (Disambiguate+EventTypes+AltKeys+AllKeys) Esc key press", L"\x1b[27;1:1u", 15, true, VK_ESCAPE, 0x01, 0, 0 },
// flags=16 (0b10000): ReportAssociatedText only (meaningless without AllKeys)
TestCase{ L"Flags=16 (AssocText) Esc key", L"\x1b", 16, true, VK_ESCAPE, 0x01, 0, 0 },
// flags=17 (0b10001): Disambiguate + AssocText
TestCase{ L"Flags=17 (Disambiguate+AssocText) Esc key", L"\x1b[27u", 17, true, VK_ESCAPE, 0x01, 0, 0 },
// flags=18 (0b10010): EventTypes + AssocText
TestCase{ L"Flags=18 (EventTypes+AssocText) Esc key", L"\x1b", 18, true, VK_ESCAPE, 0x01, 0, 0 },
// flags=19 (0b10011): Disambiguate + EventTypes + AssocText
TestCase{ L"Flags=19 (Disambiguate+EventTypes+AssocText) Esc key press", L"\x1b[27;1:1u", 19, true, VK_ESCAPE, 0x01, 0, 0 },
// flags=20 (0b10100): AltKeys + AssocText
TestCase{ L"Flags=20 (AltKeys+AssocText) Esc key", L"\x1b", 20, true, VK_ESCAPE, 0x01, 0, 0 },
// flags=21 (0b10101): Disambiguate + AltKeys + AssocText
TestCase{ L"Flags=21 (Disambiguate+AltKeys+AssocText) Esc key", L"\x1b[27u", 21, true, VK_ESCAPE, 0x01, 0, 0 },
// flags=22 (0b10110): EventTypes + AltKeys + AssocText
TestCase{ L"Flags=22 (EventTypes+AltKeys+AssocText) Esc key", L"\x1b", 22, true, VK_ESCAPE, 0x01, 0, 0 },
// flags=23 (0b10111): Disambiguate + EventTypes + AltKeys + AssocText
TestCase{ L"Flags=23 (Disambiguate+EventTypes+AltKeys+AssocText) Esc key press", L"\x1b[27;1:1u", 23, true, VK_ESCAPE, 0x01, 0, 0 },
// flags=24 (0b11000): AllKeys + AssocText
// 'a' key with text reporting: CSI 97;;97 u
TestCase{ L"Flags=24 (AllKeys+AssocText) 'a' key", L"\x1b[97;1;97u", 24, true, 'A', 0x1E, L'a', 0 },
// flags=25 (0b11001): Disambiguate + AllKeys + AssocText
TestCase{ L"Flags=25 (Disambiguate+AllKeys+AssocText) 'a' key", L"\x1b[97;1;97u", 25, true, 'A', 0x1E, L'a', 0 },
// flags=26 (0b11010): EventTypes + AllKeys + AssocText
TestCase{ L"Flags=26 (EventTypes+AllKeys+AssocText) 'a' key press", L"\x1b[97;1:1;97u", 26, true, 'A', 0x1E, L'a', 0 },
// flags=27 (0b11011): Disambiguate + EventTypes + AllKeys + AssocText
TestCase{ L"Flags=27 (Disambiguate+EventTypes+AllKeys+AssocText) 'a' key press", L"\x1b[97;1:1;97u", 27, true, 'A', 0x1E, L'a', 0 },
// flags=28 (0b11100): AltKeys + AllKeys + AssocText
TestCase{ L"Flags=28 (AltKeys+AllKeys+AssocText) 'a' key", L"\x1b[97;1;97u", 28, true, 'A', 0x1E, L'a', 0 },
// flags=29 (0b11101): Disambiguate + AltKeys + AllKeys + AssocText
TestCase{ L"Flags=29 (Disambiguate+AltKeys+AllKeys+AssocText) 'a' key", L"\x1b[97;1;97u", 29, true, 'A', 0x1E, L'a', 0 },
// flags=30 (0b11110): EventTypes + AltKeys + AllKeys + AssocText
TestCase{ L"Flags=30 (EventTypes+AltKeys+AllKeys+AssocText) 'a' key press", L"\x1b[97;1:1;97u", 30, true, 'A', 0x1E, L'a', 0 },
// flags=31 (0b11111): All flags enabled
TestCase{ L"Flags=31 (all) 'a' key press", L"\x1b[97;1:1;97u", 31, true, 'A', 0x1E, L'a', 0 },
// ====================================================================
// SECTION 2: Modifier Combinations with Disambiguate (flag=1)
// Testing all modifier permutations with 'a' key
// Kitty modifier encoding: shift=1, alt=2, ctrl=4, caps_lock=64, num_lock=128
// Transmitted value = 1 + modifiers
// ====================================================================
// Alt+'a' -> CSI 97;3 u (mod=1+2=3)
TestCase{ L"Disambiguate: Alt+a", L"\x1b[97;3u", 1, true, 'A', 0x1E, L'a', ALT_PRESSED },
// Ctrl+'a' -> CSI 97;5 u (mod=1+4=5)
TestCase{ L"Disambiguate: Ctrl+a", L"\x1b[97;5u", 1, true, 'A', 0x1E, L'\x01', CTRL_PRESSED },
// Ctrl+Alt+'a' -> CSI 97;7 u (mod=1+2+4=7)
TestCase{ L"Disambiguate: Ctrl+Alt+a", L"\x1b[97;7u", 1, true, 'A', 0x1E, L'\x01', CTRL_PRESSED | ALT_PRESSED },
// Shift+Alt+'a' -> CSI 97;4 u (mod=1+1+2=4)
TestCase{ L"Disambiguate: Shift+Alt+a", L"\x1b[97;4u", 1, true, 'A', 0x1E, L'A', SHIFT_PRESSED | ALT_PRESSED },
// ====================================================================
// SECTION 3: Modifier combinations with AllKeys (flag=8)
// All keys produce CSI u, lock keys are reported
// ====================================================================
// No modifiers: 'a' -> CSI 97 u
TestCase{ L"AllKeys: 'a' no mods", L"\x1b[97u", 8, true, 'A', 0x1E, L'a', 0 },
// Shift+'a' -> CSI 97;2 u (mod=1+1=2)
TestCase{ L"AllKeys: Shift+a", L"\x1b[97;2u", 8, true, 'A', 0x1E, L'A', SHIFT_PRESSED },
// Alt+'a' -> CSI 97;3 u (mod=1+2=3)
TestCase{ L"AllKeys: Alt+a", L"\x1b[97;3u", 8, true, 'A', 0x1E, L'a', ALT_PRESSED },
// Ctrl+'a' -> CSI 97;5 u (mod=1+4=5)
TestCase{ L"AllKeys: Ctrl+a", L"\x1b[97;5u", 8, true, 'A', 0x1E, L'\x01', CTRL_PRESSED },
// Shift+Alt+'a' -> CSI 97;4 u (mod=1+1+2=4)
TestCase{ L"AllKeys: Shift+Alt+a", L"\x1b[97;4u", 8, true, 'A', 0x1E, L'A', SHIFT_PRESSED | ALT_PRESSED },
// Shift+Ctrl+'a' -> CSI 97;6 u (mod=1+1+4=6)
TestCase{ L"AllKeys: Shift+Ctrl+a", L"\x1b[97;6u", 8, true, 'A', 0x1E, L'\x01', SHIFT_PRESSED | CTRL_PRESSED },
// Alt+Ctrl+'a' -> CSI 97;7 u (mod=1+2+4=7)
TestCase{ L"AllKeys: Alt+Ctrl+a", L"\x1b[97;7u", 8, true, 'A', 0x1E, L'\x01', ALT_PRESSED | CTRL_PRESSED },
// Shift+Alt+Ctrl+'a' -> CSI 97;8 u (mod=1+1+2+4=8)
TestCase{ L"AllKeys: Shift+Alt+Ctrl+a", L"\x1b[97;8u", 8, true, 'A', 0x1E, L'\x01', SHIFT_PRESSED | ALT_PRESSED | CTRL_PRESSED },
// CapsLock+'a' -> CSI 97;65 u (mod=1+64=65)
TestCase{ L"AllKeys: CapsLock+a", L"\x1b[97;65u", 8, true, 'A', 0x1E, L'A', CAPSLOCK_ON },
// NumLock+'a' -> CSI 97;129 u (mod=1+128=129)
TestCase{ L"AllKeys: NumLock+a", L"\x1b[97;129u", 8, true, 'A', 0x1E, L'a', NUMLOCK_ON },
// CapsLock+NumLock+'a' -> CSI 97;193 u (mod=1+64+128=193)
TestCase{ L"AllKeys: CapsLock+NumLock+a", L"\x1b[97;193u", 8, true, 'A', 0x1E, L'A', CAPSLOCK_ON | NUMLOCK_ON },
// Shift+CapsLock+'a' -> CSI 97;66 u (mod=1+1+64=66)
TestCase{ L"AllKeys: Shift+CapsLock+a", L"\x1b[97;66u", 8, true, 'A', 0x1E, L'a', SHIFT_PRESSED | CAPSLOCK_ON },
// All modifiers: Shift+Alt+Ctrl+CapsLock+NumLock
// mod=1+1+2+4+64+128=200
TestCase{ L"AllKeys: all mods", L"\x1b[97;200u", 8, true, 'A', 0x1E, L'\x01', SHIFT_PRESSED | ALT_PRESSED | CTRL_PRESSED | CAPSLOCK_ON | NUMLOCK_ON },
// ====================================================================
// SECTION 4: Enter, Tab, Backspace - Legacy behavior exceptions
// Per spec: "The only exceptions are the Enter, Tab and Backspace keys
// which still generate the same bytes as in legacy mode"
// ====================================================================
// With Disambiguate only (flag=1), these stay legacy:
// (These should return MakeUnhandled(), causing legacy processing)
// We'll test that they DON'T produce CSI u output
// With AllKeys (flag=8), they DO get CSI u encoding:
// Enter -> CSI 13 u
TestCase{ L"AllKeys: Enter", L"\x1b[13u", 8, true, VK_RETURN, 0x1C, L'\r', 0 },
// Tab -> CSI 9 u
TestCase{ L"AllKeys: Tab", L"\x1b[9u", 8, true, VK_TAB, 0x0F, L'\t', 0 },
// Backspace -> CSI 127 u
TestCase{ L"AllKeys: Backspace", L"\x1b[127u", 8, true, VK_BACK, 0x0E, L'\b', 0 },
// Enter with Shift -> CSI 13;2 u
TestCase{ L"AllKeys: Shift+Enter", L"\x1b[13;2u", 8, true, VK_RETURN, 0x1C, L'\r', SHIFT_PRESSED },
// Tab with Ctrl -> CSI 9;5 u
TestCase{ L"AllKeys: Ctrl+Tab", L"\x1b[9;5u", 8, true, VK_TAB, 0x0F, L'\t', CTRL_PRESSED },
// Backspace with Alt -> CSI 127;3 u
TestCase{ L"AllKeys: Alt+Backspace", L"\x1b[127;3u", 8, true, VK_BACK, 0x0E, L'\b', ALT_PRESSED },
// ====================================================================
// SECTION 5: Event Types (flag=2)
// press=1, repeat=2, release=3
// Format: CSI keycode;mod:event u
// ====================================================================
// Key press with Disambiguate+EventTypes (flag=3)
TestCase{ L"EventTypes: Esc press", L"\x1b[27;1:1u", 3, true, VK_ESCAPE, 0x01, 0, 0 },
// Key release with Disambiguate+EventTypes (flag=3)
TestCase{ L"EventTypes: Esc release", L"\x1b[27;1:3u", 3, false, VK_ESCAPE, 0x01, 0, 0 },
// Key press with AllKeys+EventTypes (flag=10)
TestCase{ L"EventTypes+AllKeys: 'a' press", L"\x1b[97;1:1u", 10, true, 'A', 0x1E, L'a', 0 },
// Key release with AllKeys+EventTypes (flag=10)
TestCase{ L"EventTypes+AllKeys: 'a' release", L"\x1b[97;1:3u", 10, false, 'A', 0x1E, L'a', 0 },
// Enter/Tab/Backspace release - only with AllKeys+EventTypes
// Without AllKeys, release events for these are suppressed
TestCase{ L"EventTypes+AllKeys: Enter release", L"\x1b[13;1:3u", 10, false, VK_RETURN, 0x1C, L'\r', 0 },
TestCase{ L"EventTypes+AllKeys: Tab release", L"\x1b[9;1:3u", 10, false, VK_TAB, 0x0F, L'\t', 0 },
TestCase{ L"EventTypes+AllKeys: Backspace release", L"\x1b[127;1:3u", 10, false, VK_BACK, 0x0E, L'\b', 0 },
// Press with modifier: Shift+Esc -> CSI 27;2:1 u
TestCase{ L"EventTypes: Shift+Esc press", L"\x1b[27;2:1u", 3, true, VK_ESCAPE, 0x01, 0, SHIFT_PRESSED },
// Release with modifier: Shift+Esc -> CSI 27;2:3 u
TestCase{ L"EventTypes: Shift+Esc release", L"\x1b[27;2:3u", 3, false, VK_ESCAPE, 0x01, 0, SHIFT_PRESSED },
// ====================================================================
// SECTION 6: Keypad Keys
// With Disambiguate, keypad keys get CSI u with special codepoints
// ====================================================================
// Keypad 0-9: 57399-57408
TestCase{ L"Disambiguate: Numpad0", L"\x1b[57399u", 1, true, VK_NUMPAD0, 0x52, L'0', 0 },
TestCase{ L"Disambiguate: Numpad1", L"\x1b[57400u", 1, true, VK_NUMPAD1, 0x4F, L'1', 0 },
TestCase{ L"Disambiguate: Numpad5", L"\x1b[57404u", 1, true, VK_NUMPAD5, 0x4C, L'5', 0 },
TestCase{ L"Disambiguate: Numpad9", L"\x1b[57408u", 1, true, VK_NUMPAD9, 0x49, L'9', 0 },
// Keypad operators
TestCase{ L"Disambiguate: Numpad Decimal", L"\x1b[57409u", 1, true, VK_DECIMAL, 0x53, L'.', 0 },
TestCase{ L"Disambiguate: Numpad Divide", L"\x1b[57410u", 1, true, VK_DIVIDE, 0x35, L'/', ENHANCED_KEY },
TestCase{ L"Disambiguate: Numpad Multiply", L"\x1b[57411u", 1, true, VK_MULTIPLY, 0x37, L'*', 0 },
TestCase{ L"Disambiguate: Numpad Subtract", L"\x1b[57412u", 1, true, VK_SUBTRACT, 0x4A, L'-', 0 },
TestCase{ L"Disambiguate: Numpad Add", L"\x1b[57413u", 1, true, VK_ADD, 0x4E, L'+', 0 },
// Keypad with modifiers
TestCase{ L"Disambiguate: Shift+Numpad5", L"\x1b[57404;2u", 1, true, VK_NUMPAD5, 0x4C, L'5', SHIFT_PRESSED },
TestCase{ L"Disambiguate: Ctrl+Numpad0", L"\x1b[57399;5u", 1, true, VK_NUMPAD0, 0x52, L'0', CTRL_PRESSED },
// ====================================================================
// SECTION 7: Lock Keys and Modifier Keys (with AllKeys flag=8)
// These report their own key codes
// ====================================================================
// CapsLock key itself -> CSI 57358 u
TestCase{ L"AllKeys: CapsLock key press", L"\x1b[57358u", 8, true, VK_CAPITAL, 0x3A, 0, 0 },
// NumLock key itself -> CSI 57360 u
TestCase{ L"AllKeys: NumLock key press", L"\x1b[57360u", 8, true, VK_NUMLOCK, 0x45, 0, ENHANCED_KEY },
// ScrollLock key itself -> CSI 57359 u
TestCase{ L"AllKeys: ScrollLock key press", L"\x1b[57359u", 8, true, VK_SCROLL, 0x46, 0, 0 },
// Left Shift key -> CSI 57441 u (with shift modifier set)
TestCase{ L"AllKeys: Left Shift key press", L"\x1b[57441;2u", 8, true, VK_SHIFT, 0x2A, 0, SHIFT_PRESSED },
// Right Shift key -> CSI 57447 u
TestCase{ L"AllKeys: Right Shift key press", L"\x1b[57447;2u", 8, true, VK_SHIFT, 0x36, 0, SHIFT_PRESSED },
// Left Ctrl key -> CSI 57442 u (with ctrl modifier set)
TestCase{ L"AllKeys: Left Ctrl key press", L"\x1b[57442;5u", 8, true, VK_CONTROL, 0x1D, 0, CTRL_PRESSED },
// Right Ctrl key -> CSI 57448 u
TestCase{ L"AllKeys: Right Ctrl key press", L"\x1b[57448;5u", 8, true, VK_CONTROL, 0x1D, 0, CTRL_PRESSED | ENHANCED_KEY },
// Left Alt key -> CSI 57443 u (with alt modifier set)
TestCase{ L"AllKeys: Left Alt key press", L"\x1b[57443;3u", 8, true, VK_MENU, 0x38, 0, ALT_PRESSED },
// Right Alt key -> CSI 57449 u
TestCase{ L"AllKeys: Right Alt key press", L"\x1b[57449;3u", 8, true, VK_MENU, 0x38, 0, RIGHT_ALT_PRESSED | ENHANCED_KEY },
// Left Windows key -> CSI 57444 u (super modifier not available in Win32)
TestCase{ L"AllKeys: Left Win key press", L"\x1b[57444u", 8, true, VK_LWIN, 0x5B, 0, ENHANCED_KEY },
// Right Windows key -> CSI 57450 u
TestCase{ L"AllKeys: Right Win key press", L"\x1b[57450u", 8, true, VK_RWIN, 0x5C, 0, ENHANCED_KEY },
// ====================================================================
// SECTION 8: Special Keys with Disambiguate (flag=1)
// ====================================================================
// Various special keys that get CSI u encoding
// Pause key -> CSI 57362 u
TestCase{ L"AllKeys: Pause key", L"\x1b[57362u", 8, true, VK_PAUSE, 0x45, 0, 0 },
// PrintScreen key -> CSI 57361 u
TestCase{ L"AllKeys: PrintScreen key", L"\x1b[57361u", 8, true, VK_SNAPSHOT, 0x37, 0, ENHANCED_KEY },
// Menu/Apps key -> CSI 57363 u
TestCase{ L"AllKeys: Menu key", L"\x1b[57363u", 8, true, VK_APPS, 0x5D, 0, ENHANCED_KEY },
// ====================================================================
// SECTION 9: Legacy text keys with Disambiguate (flag=1)
// Per spec: "the keys a-z 0-9 ` - = [ ] \ ; ' , . / with modifiers
// alt, ctrl, ctrl+alt, shift+alt" get CSI u encoding
// ====================================================================
// Test each punctuation key with Alt
TestCase{ L"Disambiguate: Alt+`", L"\x1b[96;3u", 1, true, VK_OEM_3, 0x29, L'`', ALT_PRESSED },
TestCase{ L"Disambiguate: Alt+-", L"\x1b[45;3u", 1, true, VK_OEM_MINUS, 0x0C, L'-', ALT_PRESSED },
TestCase{ L"Disambiguate: Alt+=", L"\x1b[61;3u", 1, true, VK_OEM_PLUS, 0x0D, L'=', ALT_PRESSED },
TestCase{ L"Disambiguate: Alt+[", L"\x1b[91;3u", 1, true, VK_OEM_4, 0x1A, L'[', ALT_PRESSED },
TestCase{ L"Disambiguate: Alt+]", L"\x1b[93;3u", 1, true, VK_OEM_6, 0x1B, L']', ALT_PRESSED },
TestCase{ L"Disambiguate: Alt+\\", L"\x1b[92;3u", 1, true, VK_OEM_5, 0x2B, L'\\', ALT_PRESSED },
TestCase{ L"Disambiguate: Alt+;", L"\x1b[59;3u", 1, true, VK_OEM_1, 0x27, L';', ALT_PRESSED },
TestCase{ L"Disambiguate: Alt+'", L"\x1b[39;3u", 1, true, VK_OEM_7, 0x28, L'\'', ALT_PRESSED },
TestCase{ L"Disambiguate: Alt+,", L"\x1b[44;3u", 1, true, VK_OEM_COMMA, 0x33, L',', ALT_PRESSED },
TestCase{ L"Disambiguate: Alt+.", L"\x1b[46;3u", 1, true, VK_OEM_PERIOD, 0x34, L'.', ALT_PRESSED },
TestCase{ L"Disambiguate: Alt+/", L"\x1b[47;3u", 1, true, VK_OEM_2, 0x35, L'/', ALT_PRESSED },
// Test numbers with Ctrl
TestCase{ L"Disambiguate: Ctrl+0", L"\x1b[48;5u", 1, true, '0', 0x0B, L'0', CTRL_PRESSED },
TestCase{ L"Disambiguate: Ctrl+1", L"\x1b[49;5u", 1, true, '1', 0x02, L'1', CTRL_PRESSED },
TestCase{ L"Disambiguate: Ctrl+9", L"\x1b[57;5u", 1, true, '9', 0x0A, L'9', CTRL_PRESSED },
// Test letters with Ctrl+Alt
TestCase{ L"Disambiguate: Ctrl+Alt+a", L"\x1b[97;7u", 1, true, 'A', 0x1E, L'\x01', CTRL_PRESSED | ALT_PRESSED },
TestCase{ L"Disambiguate: Ctrl+Alt+z", L"\x1b[122;7u", 1, true, 'Z', 0x2C, L'\x1A', CTRL_PRESSED | ALT_PRESSED },
// ====================================================================
// SECTION 10: Navigation keys as keypad (without ENHANCED_KEY)
// When ENHANCED_KEY is not set, navigation keys are from the keypad
// ====================================================================
// Home without ENHANCED_KEY -> KP_HOME (57423)
TestCase{ L"AllKeys: Keypad Home", L"\x1b[57423u", 8, true, VK_HOME, 0x47, 0, 0 },
// End without ENHANCED_KEY -> KP_END (57424)
TestCase{ L"AllKeys: Keypad End", L"\x1b[57424u", 8, true, VK_END, 0x4F, 0, 0 },
// Insert without ENHANCED_KEY -> KP_INSERT (57425)
TestCase{ L"AllKeys: Keypad Insert", L"\x1b[57425u", 8, true, VK_INSERT, 0x52, 0, 0 },
// Delete without ENHANCED_KEY -> KP_DELETE (57426)
TestCase{ L"AllKeys: Keypad Delete", L"\x1b[57426u", 8, true, VK_DELETE, 0x53, 0, 0 },
// PageUp without ENHANCED_KEY -> KP_PAGE_UP (57421)
TestCase{ L"AllKeys: Keypad PageUp", L"\x1b[57421u", 8, true, VK_PRIOR, 0x49, 0, 0 },
// PageDown without ENHANCED_KEY -> KP_PAGE_DOWN (57422)
TestCase{ L"AllKeys: Keypad PageDown", L"\x1b[57422u", 8, true, VK_NEXT, 0x51, 0, 0 },
// Arrows without ENHANCED_KEY
TestCase{ L"AllKeys: Keypad Up", L"\x1b[57419u", 8, true, VK_UP, 0x48, 0, 0 },
TestCase{ L"AllKeys: Keypad Down", L"\x1b[57420u", 8, true, VK_DOWN, 0x50, 0, 0 },
TestCase{ L"AllKeys: Keypad Left", L"\x1b[57417u", 8, true, VK_LEFT, 0x4B, 0, 0 },
TestCase{ L"AllKeys: Keypad Right", L"\x1b[57418u", 8, true, VK_RIGHT, 0x4D, 0, 0 },
// ====================================================================
// SECTION 11: Media Keys
// ====================================================================
TestCase{ L"AllKeys: Media Play/Pause", L"\x1b[57430u", 8, true, VK_MEDIA_PLAY_PAUSE, 0, 0, 0 },
TestCase{ L"AllKeys: Media Stop", L"\x1b[57432u", 8, true, VK_MEDIA_STOP, 0, 0, 0 },
TestCase{ L"AllKeys: Media Next Track", L"\x1b[57435u", 8, true, VK_MEDIA_NEXT_TRACK, 0, 0, 0 },
TestCase{ L"AllKeys: Media Prev Track", L"\x1b[57436u", 8, true, VK_MEDIA_PREV_TRACK, 0, 0, 0 },
TestCase{ L"AllKeys: Volume Down", L"\x1b[57438u", 8, true, VK_VOLUME_DOWN, 0, 0, 0 },
TestCase{ L"AllKeys: Volume Up", L"\x1b[57439u", 8, true, VK_VOLUME_UP, 0, 0, 0 },
TestCase{ L"AllKeys: Volume Mute", L"\x1b[57440u", 8, true, VK_VOLUME_MUTE, 0, 0, 0 },
// ====================================================================
// SECTION 12: Function Keys (F13-F24)
// F1-F12 use legacy sequences, F13-F24 use CSI u with codes 57376-57387
// ====================================================================
TestCase{ L"AllKeys: F13", L"\x1b[57376u", 8, true, VK_F13, 0x64, 0, 0 },
TestCase{ L"AllKeys: F14", L"\x1b[57377u", 8, true, VK_F14, 0x65, 0, 0 },
TestCase{ L"AllKeys: F15", L"\x1b[57378u", 8, true, VK_F15, 0x66, 0, 0 },
TestCase{ L"AllKeys: F16", L"\x1b[57379u", 8, true, VK_F16, 0x67, 0, 0 },
TestCase{ L"AllKeys: F17", L"\x1b[57380u", 8, true, VK_F17, 0x68, 0, 0 },
TestCase{ L"AllKeys: F18", L"\x1b[57381u", 8, true, VK_F18, 0x69, 0, 0 },
TestCase{ L"AllKeys: F19", L"\x1b[57382u", 8, true, VK_F19, 0x6A, 0, 0 },
TestCase{ L"AllKeys: F20", L"\x1b[57383u", 8, true, VK_F20, 0x6B, 0, 0 },
TestCase{ L"AllKeys: F21", L"\x1b[57384u", 8, true, VK_F21, 0x6C, 0, 0 },
TestCase{ L"AllKeys: F22", L"\x1b[57385u", 8, true, VK_F22, 0x6D, 0, 0 },
TestCase{ L"AllKeys: F23", L"\x1b[57386u", 8, true, VK_F23, 0x6E, 0, 0 },
TestCase{ L"AllKeys: F24", L"\x1b[57387u", 8, true, VK_F24, 0x76, 0, 0 },
// F13 with modifiers
TestCase{ L"AllKeys: Shift+F13", L"\x1b[57376;2u", 8, true, VK_F13, 0x64, 0, SHIFT_PRESSED },
TestCase{ L"AllKeys: Ctrl+F13", L"\x1b[57376;5u", 8, true, VK_F13, 0x64, 0, CTRL_PRESSED },
TestCase{ L"AllKeys: Alt+F13", L"\x1b[57376;3u", 8, true, VK_F13, 0x64, 0, ALT_PRESSED },
// ====================================================================
// SECTION 13: Alternate Keys (ReportAlternateKeys flag = 4)
// Format: CSI keycode:shifted-key:base-layout-key ; modifiers u
// Shifted key is present only when shift modifier is active
// Base layout key is the PC-101 US keyboard equivalent
// ====================================================================
// Shift+a with AltKeys flag: 97:65 (a:A) - shifted key is 'A' (65)
// flags = AllKeys(8) + AltKeys(4) = 12
TestCase{ L"AltKeys+AllKeys: Shift+a", L"\x1b[97:65;2u", 12, true, 'A', 0x1E, L'A', SHIFT_PRESSED },
// Shift+1 with AltKeys flag: 49:33 (1:!) - shifted key is '!' (33)
TestCase{ L"AltKeys+AllKeys: Shift+1", L"\x1b[49:33;2u", 12, true, '1', 0x02, L'!', SHIFT_PRESSED },
// Shift+[ with AltKeys flag: 91:123 ([:{) - shifted key is '{' (123)
TestCase{ L"AltKeys+AllKeys: Shift+[", L"\x1b[91:123;2u", 12, true, VK_OEM_4, 0x1A, L'{', SHIFT_PRESSED },
// Without shift, no shifted key is reported
// 'a' with AltKeys flag (no shift): 97 only, no alternate keys
TestCase{ L"AltKeys+AllKeys: a (no shift)", L"\x1b[97u", 12, true, 'A', 0x1E, L'a', 0 },
// ====================================================================
// SECTION 14: Complex combinations
// Testing multiple flags together with various keys and modifiers
// ====================================================================
// AllKeys + EventTypes + CapsLock: 'a' press with CapsLock
// mod=1+64=65, event=press=1
TestCase{ L"AllKeys+EventTypes: CapsLock+a press", L"\x1b[97;65:1u", 10, true, 'A', 0x1E, L'A', CAPSLOCK_ON },
// AllKeys + EventTypes + all modifiers: press
// mod=1+1+2+4+64+128=200, event=1
TestCase{ L"AllKeys+EventTypes: all mods press", L"\x1b[97;200:1u", 10, true, 'A', 0x1E, L'\x01', SHIFT_PRESSED | ALT_PRESSED | CTRL_PRESSED | CAPSLOCK_ON | NUMLOCK_ON },
// AllKeys + EventTypes + all modifiers: release
TestCase{ L"AllKeys+EventTypes: all mods release", L"\x1b[97;200:3u", 10, false, 'A', 0x1E, L'\x01', SHIFT_PRESSED | ALT_PRESSED | CTRL_PRESSED | CAPSLOCK_ON | NUMLOCK_ON },
// ====================================================================
// SECTION 15: Text with associated codepoints (flag=24: AllKeys + AssocText)
// Format: CSI keycode ; modifiers ; text u
// ====================================================================
// 'A' (shifted) with AssocText: CSI 97;2;65 u
TestCase{ L"AllKeys+AssocText: Shift+a", L"\x1b[97;2;65u", 24, true, 'A', 0x1E, L'A', SHIFT_PRESSED },
// Number with shift (symbol): Shift+1 -> '!'
// CSI 49;2;33 u (49='1', 33='!')
TestCase{ L"AllKeys+AssocText: Shift+1", L"\x1b[49;2;33u", 24, true, '1', 0x02, L'!', SHIFT_PRESSED },
// Ctrl+a produces control character (0x01), which should not be in text
// Text field should be omitted for control codes
TestCase{ L"AllKeys+AssocText: Ctrl+a (no text)", L"\x1b[97;5;1u", 24, true, 'A', 0x1E, L'\x01', CTRL_PRESSED },
// ====================================================================
// SECTION 16: Edge cases
// ====================================================================
// Keypad Enter (ENHANCED_KEY set) -> KP_ENTER (57414)
TestCase{ L"AllKeys: Keypad Enter", L"\x1b[57414u", 8, true, VK_RETURN, 0x1C, L'\r', ENHANCED_KEY },
// Regular Enter vs Keypad Enter distinction
TestCase{ L"AllKeys: Regular Enter", L"\x1b[13u", 8, true, VK_RETURN, 0x1C, L'\r', 0 },
// Escape with all basic modifiers
TestCase{ L"AllKeys: Shift+Alt+Ctrl+Esc", L"\x1b[27;8u", 8, true, VK_ESCAPE, 0x01, 0, SHIFT_PRESSED | ALT_PRESSED | CTRL_PRESSED },
// Tab with Shift (special legacy: CSI Z, but with AllKeys should be CSI 9;2 u)
TestCase{ L"AllKeys: Shift+Tab", L"\x1b[9;2u", 8, true, VK_TAB, 0x0F, 0, SHIFT_PRESSED },
};
}
extern "C" HRESULT __declspec(dllexport) __cdecl KittyKeyTestDataSource(IDataSource** ppDataSource, void*)
{
*ppDataSource = new ArrayIndexTaefAdapterSource(std::size(testCases));
return S_OK;
}
class KittyKeyboardProtocolTests
{
TEST_CLASS(KittyKeyboardProtocolTests);
TEST_METHOD(KeyPressTests)
{
BEGIN_TEST_METHOD_PROPERTIES()
TEST_METHOD_PROPERTY(L"DataSource", L"Export:KittyKeyTestDataSource")
END_TEST_METHOD_PROPERTIES()
DisableVerifyExceptions disableVerifyExceptions{};
SetVerifyOutput verifyOutputScope{ VerifyOutputSettings::LogOnlyFailures };
size_t i{};
TestData::TryGetValue(L"index", i);
const auto& tc = testCases[i];
Log::Comment(NoThrowString().Format(L"[%zu] Test case \"%.*s\"", i, tc.name.size(), tc.name.data()));
auto input = createInput(tc.flags);
const auto expected = TerminalInput::MakeOutput(tc.expected);
const auto actual = process(input, tc.keyDown, tc.vk, tc.sc, tc.ch, tc.state);
VERIFY_ARE_EQUAL(expected, actual);
}
// Repeat events require stateful testing - the same key must be pressed twice
// without a release in between. This cannot be done with the data-driven approach.
TEST_METHOD(KeyRepeatEvents)
{
Log::Comment(L"Testing key repeat event type (event type = 2)");
// Use EventTypes flag (2) + AllKeys flag (8) = 10
constexpr uint8_t flags = 10;
auto input = createInput(flags);
// First press -> event type 1 (press)
auto result1 = process(input, true, 'A', 0x1E, L'a', 0);
auto expected1 = TerminalInput::MakeOutput(L"\x1b[97;1:1u");
VERIFY_ARE_EQUAL(expected1, result1, L"First press should be event type 1");
// Second press (same key, no release) -> event type 2 (repeat)
auto result2 = process(input, true, 'A', 0x1E, L'a', 0);
auto expected2 = TerminalInput::MakeOutput(L"\x1b[97;1:2u");
VERIFY_ARE_EQUAL(expected2, result2, L"Second press should be event type 2 (repeat)");
// Third press (still same key) -> still event type 2 (repeat)
auto result3 = process(input, true, 'A', 0x1E, L'a', 0);
auto expected3 = TerminalInput::MakeOutput(L"\x1b[97;1:2u");
VERIFY_ARE_EQUAL(expected3, result3, L"Third press should still be event type 2 (repeat)");
// Release -> event type 3
auto result4 = process(input, false, 'A', 0x1E, L'a', 0);
auto expected4 = TerminalInput::MakeOutput(L"\x1b[97;1:3u");
VERIFY_ARE_EQUAL(expected4, result4, L"Release should be event type 3");
// Next press after release -> event type 1 (press) again
auto result5 = process(input, true, 'A', 0x1E, L'a', 0);
auto expected5 = TerminalInput::MakeOutput(L"\x1b[97;1:1u");
VERIFY_ARE_EQUAL(expected5, result5, L"Press after release should be event type 1 again");
}
// Test repeat events with modifiers
TEST_METHOD(KeyRepeatEventsWithModifiers)
{
Log::Comment(L"Testing key repeat with Shift modifier");
constexpr uint8_t flags = 10; // EventTypes + AllKeys
auto input = createInput(flags);
// First Shift+a press -> event type 1
auto result1 = process(input, true, 'A', 0x1E, L'A', SHIFT_PRESSED);
auto expected1 = TerminalInput::MakeOutput(L"\x1b[97;2:1u");
VERIFY_ARE_EQUAL(expected1, result1, L"First Shift+a press should be event type 1");
// Repeat Shift+a -> event type 2
auto result2 = process(input, true, 'A', 0x1E, L'A', SHIFT_PRESSED);
auto expected2 = TerminalInput::MakeOutput(L"\x1b[97;2:2u");
VERIFY_ARE_EQUAL(expected2, result2, L"Repeat Shift+a should be event type 2");
}
// Test that pressing different keys resets repeat detection
TEST_METHOD(KeyRepeatResetOnDifferentKey)
{
Log::Comment(L"Testing that pressing a different key resets repeat detection");
constexpr uint8_t flags = 10; // EventTypes + AllKeys
auto input = createInput(flags);
// Press 'a'
auto result1 = process(input, true, 'A', 0x1E, L'a', 0);
auto expected1 = TerminalInput::MakeOutput(L"\x1b[97;1:1u");
VERIFY_ARE_EQUAL(expected1, result1, L"First 'a' press should be event type 1");
// Press 'b' (different key) -> should be press, not repeat
auto result2 = process(input, true, 'B', 0x30, L'b', 0);
auto expected2 = TerminalInput::MakeOutput(L"\x1b[98;1:1u");
VERIFY_ARE_EQUAL(expected2, result2, L"'b' press should be event type 1 (not repeat)");
// Press 'a' again -> should be press since 'b' was pressed in between
auto result3 = process(input, true, 'A', 0x1E, L'a', 0);
auto expected3 = TerminalInput::MakeOutput(L"\x1b[97;1:1u");
VERIFY_ARE_EQUAL(expected3, result3, L"'a' press after 'b' should be event type 1 (new press)");
}
// Test Enter/Tab/Backspace release suppression without AllKeys
TEST_METHOD(EnterTabBackspaceReleaseWithoutAllKeys)
{
Log::Comment(L"Testing that Enter/Tab/Backspace don't report release without AllKeys flag");
// Use Disambiguate + EventTypes (flags = 3), but NOT AllKeys
constexpr uint8_t flags = 3;
auto input = createInput(flags);
// These keys should NOT produce output for release events
// (they return MakeUnhandled for press too with just Disambiguate,
// but release should produce _makeNoOutput)
// Note: With flags=3 (no AllKeys), Enter/Tab/Backspace use legacy encoding
// and release events should be suppressed (return no output)
}
// Test that without EventTypes flag, release events produce no output
TEST_METHOD(ReleaseEventsWithoutEventTypesFlag)
{
Log::Comment(L"Testing that release events produce no output without EventTypes flag");
// Use only AllKeys (flag = 8), NOT EventTypes
constexpr uint8_t flags = 8;
auto input = createInput(flags);
// Press should produce output
auto result1 = process(input, true, 'A', 0x1E, L'a', 0);
auto expected1 = TerminalInput::MakeOutput(L"\x1b[97u");
VERIFY_ARE_EQUAL(expected1, result1, L"Press should produce output");
// Release should produce no output (empty optional)
auto result2 = process(input, false, 'A', 0x1E, L'a', 0);
VERIFY_IS_FALSE(result2.has_value(), L"Release without EventTypes flag should produce no output");
}
// Test legacy mode (flags=0) produces MakeUnhandled for regular keys
TEST_METHOD(LegacyModePassthrough)
{
Log::Comment(L"Testing that legacy mode (flags=0) returns MakeUnhandled for regular keys");
constexpr uint8_t flags = 0;
auto input = createInput(flags);
// Regular key 'a' should return MakeUnhandled (falls through to legacy processing)
auto result = process(input, true, 'A', 0x1E, L'a', 0);
auto unhandled = TerminalInput::MakeUnhandled();
VERIFY_ARE_EQUAL(unhandled, result, L"Regular key in legacy mode should be unhandled");
}
};

File diff suppressed because it is too large Load Diff

View File

@@ -78,8 +78,8 @@ namespace Microsoft::Console::VirtualTerminal
// Kitty keyboard protocol methods
void SetKittyKeyboardProtocol(uint8_t flags, KittyKeyboardProtocolMode mode) noexcept;
uint8_t GetKittyFlags() const noexcept;
void PushKittyFlags(uint8_t flags) noexcept;
void PopKittyFlags(size_t count) noexcept;
void PushKittyFlags(uint8_t flags);
void PopKittyFlags(size_t count);
void ResetKittyKeyboardProtocols() noexcept;
#pragma region MouseInput
@@ -96,6 +96,31 @@ namespace Microsoft::Console::VirtualTerminal
uint16_t len;
};
struct KeyEncodingInfo
{
// If not zero, this value represents the first field in the Kitty
// Keyboard Protocol (KKP) CSI u sequence. If the KKP is requested,
// this field will be preferred over the following fields.
int32_t kittyKeyCode = 0;
// A non-zero csiFinal value indicates that this key
// should be encoded as `CSI $csiParam1 ; $csiFinal`.
wchar_t csiFinal = 0;
int32_t csiParam1 = 0;
// A non-zero ss3Final value indicates that this key
// should be encoded as `ESC O $ss3Final`.
wchar_t ss3Final = 0;
// Any other encoding ends up as a non-zero plain value.
// For instance, the Tab key gets translated to a plain "\t".
std::wstring_view plain;
// If true, and Alt is pressed, an ESC prefix should be added to
// the final sequence. This only applies to non-KKP encodings.
bool altPrefix = false;
};
// storage location for the leading surrogate of a utf-16 surrogate pair
wchar_t _leadingSurrogate = 0;
@@ -118,19 +143,18 @@ namespace Microsoft::Console::VirtualTerminal
std::vector<uint8_t> _kittyMainStack;
std::vector<uint8_t> _kittyAltStack;
const wchar_t* _csi = L"\x1B[";
const wchar_t* _ss3 = L"\x1BO";
static constexpr std::wstring_view _csi{ L"\x1B[" };
static constexpr std::wstring_view _ss3{ L"\x1BO" };
void _initKeyboardMap() noexcept;
DWORD _trackControlKeyState(const KEY_EVENT_RECORD& key) noexcept;
static std::array<byte, 256> _getKeyboardState(WORD virtualKeyCode, DWORD controlKeyState);
[[nodiscard]] static wchar_t _makeCtrlChar(wchar_t ch);
[[nodiscard]] StringType _makeCharOutput(wchar_t ch);
[[nodiscard]] static StringType _makeCharOutput(uint32_t ch);
[[nodiscard]] static StringType _makeNoOutput() noexcept;
void _escapeOutput(StringType& charSequence, bool altIsPressed) const;
[[nodiscard]] OutputType _makeWin32Output(const KEY_EVENT_RECORD& key) const;
[[nodiscard]] OutputType _makeKittyOutput(const KEY_EVENT_RECORD& key, DWORD controlKeyState);
static int32_t _getKittyKeyCode(const KEY_EVENT_RECORD& key, DWORD controlKeyState) noexcept;
[[nodiscard]] KeyEncodingInfo _getKeyEncodingInfo(const KEY_EVENT_RECORD& key, DWORD simpleKeyState) const noexcept;
std::vector<uint8_t>& _getKittyStack() noexcept;
static bool _codepointIsText(uint32_t cp) noexcept;
static CodepointBuffer _codepointToBuffer(uint32_t cp) noexcept;

File diff suppressed because it is too large Load Diff