Compare commits

...

8 Commits

Author SHA1 Message Date
Leonard Hecker
940e8d5c2a wip 2026-02-04 00:44:16 +01:00
Leonard Hecker
173f897751 Merge remote-tracking branch 'origin/main' into dev/lhecker/11509-kitty-keyboard-protocol-wip 2026-02-03 20:05:36 +01:00
Leonard Hecker
d875ff4d6c wip 2026-02-03 20:04:56 +01:00
Leonard Hecker
ad501c9d92 wip 2026-02-03 00:07:02 +01:00
Leonard Hecker
cb88820fa6 wip 2026-02-02 14:12:35 +01:00
Leonard Hecker
a65bff17d8 wip 2026-02-02 14:02:11 +01:00
Leonard Hecker
5d0e8c238c Spel 2026-01-30 00:01:10 +01:00
Leonard Hecker
07792774f6 Implement the Kitty Keyboard Protocol 2026-01-29 23:58:10 +01:00
40 changed files with 3313 additions and 618 deletions

View File

@@ -866,6 +866,7 @@ KILLACTIVE
KILLFOCUS
kinda
KIYEOK
KKP
KLF
KLMNO
KOK
@@ -885,6 +886,7 @@ LBUTTONDOWN
LBUTTONUP
lcb
lci
LCMAP
LCONTROL
LCTRL
lcx

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

@@ -121,6 +121,7 @@ namespace Microsoft.Terminal.Core
String WordDelimiters { get; };
Boolean ForceVTInput { get; };
Boolean AllowKittyKeyboardMode { get; };
Boolean AllowVtChecksumReport { get; };
Boolean AllowVtClipboardWrite { get; };
Boolean TrimBlockSelection { get; };

View File

@@ -98,6 +98,7 @@ void Terminal::UpdateSettings(ICoreSettings settings)
}
_getTerminalInput().ForceDisableWin32InputMode(settings.ForceVTInput());
_getTerminalInput().ForceDisableKittyKeyboardProtocol(!settings.AllowKittyKeyboardMode());
if (settings.TabColor() == nullptr)
{

View File

@@ -349,6 +349,7 @@ namespace winrt::Microsoft::Terminal::Settings
_ReloadEnvironmentVariables = profile.ReloadEnvironmentVariables();
_RainbowSuggestions = profile.RainbowSuggestions();
_ForceVTInput = profile.ForceVTInput();
_AllowKittyKeyboardMode = profile.AllowKittyKeyboardMode();
_AllowVtChecksumReport = profile.AllowVtChecksumReport();
_AllowVtClipboardWrite = profile.AllowVtClipboardWrite();
_PathTranslationStyle = profile.PathTranslationStyle();

View File

@@ -155,6 +155,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
OBSERVABLE_PROJECTED_SETTING(_profile, AutoMarkPrompts);
OBSERVABLE_PROJECTED_SETTING(_profile, RepositionCursorWithMouse);
OBSERVABLE_PROJECTED_SETTING(_profile, ForceVTInput);
OBSERVABLE_PROJECTED_SETTING(_profile, AllowKittyKeyboardMode);
OBSERVABLE_PROJECTED_SETTING(_profile, AllowVtChecksumReport);
OBSERVABLE_PROJECTED_SETTING(_profile, AllowVtClipboardWrite);
OBSERVABLE_PROJECTED_SETTING(_profile, AnswerbackMessage);

View File

@@ -140,6 +140,7 @@ namespace Microsoft.Terminal.Settings.Editor
OBSERVABLE_PROJECTED_PROFILE_SETTING(Boolean, AutoMarkPrompts);
OBSERVABLE_PROJECTED_PROFILE_SETTING(Boolean, RepositionCursorWithMouse);
OBSERVABLE_PROJECTED_PROFILE_SETTING(Boolean, ForceVTInput);
OBSERVABLE_PROJECTED_PROFILE_SETTING(Boolean, AllowKittyKeyboardMode);
OBSERVABLE_PROJECTED_PROFILE_SETTING(Boolean, AllowVtChecksumReport);
OBSERVABLE_PROJECTED_PROFILE_SETTING(String, AnswerbackMessage);
OBSERVABLE_PROJECTED_PROFILE_SETTING(Boolean, RainbowSuggestions);

View File

@@ -49,6 +49,15 @@
Style="{StaticResource ToggleSwitchInExpanderStyle}" />
</local:SettingContainer>
<!-- Kitty Keyboard Mode -->
<local:SettingContainer x:Uid="Profile_AllowKittyKeyboardMode"
ClearSettingValue="{x:Bind Profile.ClearAllowKittyKeyboardMode}"
HasSettingValue="{x:Bind Profile.HasAllowKittyKeyboardMode, Mode=OneWay}"
SettingOverrideSource="{x:Bind Profile.AllowKittyKeyboardModeOverrideSource, Mode=OneWay}">
<ToggleSwitch IsOn="{x:Bind Profile.AllowKittyKeyboardMode, Mode=TwoWay}"
Style="{StaticResource ToggleSwitchInExpanderStyle}" />
</local:SettingContainer>
<!-- Allow VT Checksum Report -->
<local:SettingContainer x:Uid="Profile_AllowVtChecksumReport"
ClearSettingValue="{x:Bind Profile.ClearAllowVtChecksumReport}"

View File

@@ -546,6 +546,14 @@
<value>Use the legacy input encoding</value>
<comment>Header for a control to toggle legacy input encoding for the terminal.</comment>
</data>
<data name="Profile_AllowKittyKeyboardMode.Header" xml:space="preserve">
<value>Kitty keyboard protocol mode</value>
<comment>Header for a control to set the kitty keyboard protocol mode.</comment>
</data>
<data name="Profile_AllowKittyKeyboardMode.HelpText" xml:space="preserve">
<value>Sets the baseline flags for the kitty keyboard protocol. Value is a sum of: 1=Disambiguate, 2=Report event types, 4=Report alternate keys, 8=Report all keys, 16=Report text.</value>
<comment>Additional description for what the "kitty keyboard mode" setting does.</comment>
</data>
<data name="Profile_AllowVtChecksumReport.Header" xml:space="preserve">
<value>Allow DECRQCRA (Request Checksum of Rectangular Area)</value>
<comment>{Locked="DECRQCRA"}{Locked="Request Checksum of Rectangular Area"}Header for a control to toggle support for the DECRQCRA control sequence.</comment>
@@ -2583,19 +2591,19 @@
<comment>An option to choose from for the "path translation" setting.</comment>
</data>
<data name="Profile_PathTranslationStyleWsl.Content" xml:space="preserve">
<value>WSL (C:\ -> /mnt/c)</value>
<value>WSL (C:\ -&gt; /mnt/c)</value>
<comment>{Locked="WSL","C:\","/mnt/c"} An option to choose from for the "path translation" setting.</comment>
</data>
<data name="Profile_PathTranslationStyleCygwin.Content" xml:space="preserve">
<value>Cygwin (C:\ -> /cygdrive/c)</value>
<value>Cygwin (C:\ -&gt; /cygdrive/c)</value>
<comment>{Locked="Cygwin","C:\","/cygdrive/c"} An option to choose from for the "path translation" setting.</comment>
</data>
<data name="Profile_PathTranslationStyleMsys2.Content" xml:space="preserve">
<value>MSYS2 (C:\ -> /c)</value>
<value>MSYS2 (C:\ -&gt; /c)</value>
<comment>{Locked="MSYS2","C:\","/c"} An option to choose from for the "path translation" setting.</comment>
</data>
<data name="Profile_PathTranslationStyleMinGW.Content" xml:space="preserve">
<value>MinGW (C:\ -> C:/)</value>
<value>MinGW (C:\ -&gt; C:/)</value>
<comment>{Locked="MinGW","C:\","C:/"} An option to choose from for the "path translation" setting.</comment>
</data>
<data name="Profile_Delete_Orphaned.Header" xml:space="preserve">

View File

@@ -103,6 +103,7 @@ Author(s):
X(bool, ReloadEnvironmentVariables, "compatibility.reloadEnvironmentVariables", true) \
X(bool, RainbowSuggestions, "experimental.rainbowSuggestions", false) \
X(bool, ForceVTInput, "compatibility.input.forceVT", false) \
X(bool, AllowKittyKeyboardMode, "compatibility.kittyKeyboardMode", true) \
X(bool, AllowVtChecksumReport, "compatibility.allowDECRQCRA", false) \
X(bool, AllowVtClipboardWrite, "compatibility.allowOSC52", true) \
X(bool, AllowKeypadMode, "compatibility.allowDECNKM", false) \

View File

@@ -88,6 +88,7 @@ namespace Microsoft.Terminal.Settings.Model
INHERITABLE_PROFILE_SETTING(Boolean, ReloadEnvironmentVariables);
INHERITABLE_PROFILE_SETTING(Boolean, RainbowSuggestions);
INHERITABLE_PROFILE_SETTING(Boolean, ForceVTInput);
INHERITABLE_PROFILE_SETTING(Boolean, AllowKittyKeyboardMode);
INHERITABLE_PROFILE_SETTING(Boolean, AllowVtChecksumReport);
INHERITABLE_PROFILE_SETTING(Boolean, AllowKeypadMode);
INHERITABLE_PROFILE_SETTING(Boolean, AllowVtClipboardWrite);

View File

@@ -49,6 +49,7 @@
X(bool, TrimBlockSelection, true) \
X(bool, SuppressApplicationTitle) \
X(bool, ForceVTInput, false) \
X(bool, AllowKittyKeyboardMode, true) \
X(winrt::hstring, StartingTitle) \
X(bool, DetectURLs, true) \
X(bool, AutoMarkPrompts) \

View File

@@ -95,7 +95,7 @@
<!-- For ALL build types-->
<PropertyGroup Label="Configuration">
<PlatformToolset>v143</PlatformToolset>
<PlatformToolset>v145</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
<LinkIncremental>false</LinkIncremental>
<PreferredToolArchitecture>x64</PreferredToolArchitecture>

View File

@@ -117,6 +117,14 @@ using namespace Microsoft::Console::Interactivity;
CATCH_RETURN();
}
// TODO: Avoid translating win32im sequences to Kitty Keyboard Protocol temporarily.
// This is because as of this writing, our implementation is brand new, and Windows Terminal
// needs a toggle to disable it. That only works if ConPTY then doesn't do it anyway.
if (const auto inputBuffer = ServiceLocator::LocateGlobals().getConsoleInformation().pInputBuffer)
{
inputBuffer->GetTerminalInput().ForceDisableKittyKeyboardProtocol(true);
}
// The only way we're initialized is if the args said we're in conpty mode.
// If the args say so, then at least one of in, out, or signal was specified
_state = State::Initialized;

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

@@ -1151,6 +1151,8 @@ void AtlasEngine::_mapComplex(IDWriteFontFace2* mappedFontFace, u32 idx, u32 len
const size_t col2 = _api.bufferLineColumn[a.textPosition + i];
const auto fg = colors[col1 << shift];
// TODO: Instead of aligning each DWrite-cluster to the cell grid,
// we should align each grapheme cluster to the cell grid.
const auto expectedAdvance = (col2 - col1) * _p.s->font->cellSize.x;
f32 actualAdvance = 0;
for (auto j = prevCluster; j < nextCluster; ++j)

View File

@@ -67,6 +67,10 @@ 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) 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
virtual void SetTopBottomScrollingMargins(const VTInt topMargin, const VTInt bottomMargin) = 0; // DECSTBM
virtual void SetLeftRightScrollingMargins(const VTInt leftMargin, const VTInt rightMargin) = 0; // DECSLRM
virtual void EnquireAnswerback() = 0; // ENQ

View File

@@ -2054,6 +2054,35 @@ void AdaptDispatch::SetKeypadMode(const bool fApplicationMode) noexcept
_terminalInput.SetInputMode(TerminalInput::Mode::Keypad, fApplicationMode);
}
// CSI = flags ; mode u - Sets kitty keyboard protocol flags
void AdaptDispatch::SetKittyKeyboardProtocol(const VTParameter flags, const VTParameter mode) noexcept
{
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);
}
// CSI ? u - Queries current kitty keyboard protocol flags
void AdaptDispatch::QueryKittyKeyboardProtocol()
{
const auto flags = static_cast<VTInt>(_terminalInput.GetKittyFlags());
_ReturnCsiResponse(fmt::format(FMT_COMPILE(L"?{}u"), flags));
}
// CSI > flags u - Pushes current kitty keyboard flags onto the stack and sets new flags
void AdaptDispatch::PushKittyKeyboardProtocol(const VTParameter flags)
{
const auto kittyFlags = gsl::narrow_cast<uint8_t>(flags.value_or(0));
_terminalInput.PushKittyFlags(kittyFlags);
}
// CSI < count u - Pops one or more entries from the kitty keyboard stack
void AdaptDispatch::PopKittyKeyboardProtocol(const VTParameter count)
{
const auto popCount = static_cast<size_t>(count.value_or(1));
_terminalInput.PopKittyFlags(popCount);
}
// Routine Description:
// - Internal logic for adding or removing lines in the active screen buffer.
// This also moves the cursor to the left margin, which is expected behavior for IL and DL.

View File

@@ -97,6 +97,10 @@ 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) 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
void SetTopBottomScrollingMargins(const VTInt topMargin,
const VTInt bottomMargin) override; // DECSTBM
void SetLeftRightScrollingMargins(const VTInt leftMargin,

View File

@@ -54,6 +54,10 @@ 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*/) 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
void SetTopBottomScrollingMargins(const VTInt /*topMargin*/, const VTInt /*bottomMargin*/) override {} // DECSTBM
void SetLeftRightScrollingMargins(const VTInt /*leftMargin*/, const VTInt /*rightMargin*/) override {} // DECSLRM
void EnquireAnswerback() override {} // ENQ

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,721 @@
// 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, L'\x1b', 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, L'\x1b', 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[27u", 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, L'\x1b', 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, L'\x1b', 0 },
// flags=7 (0b00111): Disambiguate + EventTypes + AltKeys
TestCase{ L"Flags=7 (Disambiguate+EventTypes+AltKeys) Esc key press", L"\x1b[27u", 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[27u", 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[27u", 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[27u", 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[27u", 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, L'\x1b', 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, L'\x1b', 0 },
// flags=19 (0b10011): Disambiguate + EventTypes + AssocText
TestCase{ L"Flags=19 (Disambiguate+EventTypes+AssocText) Esc key press", L"\x1b[27u", 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, L'\x1b', 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, L'\x1b', 0 },
// flags=23 (0b10111): Disambiguate + EventTypes + AltKeys + AssocText
TestCase{ L"Flags=23 (Disambiguate+EventTypes+AltKeys+AssocText) Esc key press", L"\x1b[27u", 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;;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;;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;;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;;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;;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;;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;;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;;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[27u", 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[97u", 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 u
TestCase{ L"EventTypes: Shift+Esc press", L"\x1b[27;2u", 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;65u", 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;200u", 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;5u", 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);
const auto msg = fmt::format(L"{} != {}", til::visualize_control_codes(expected.value_or({})), til::visualize_control_codes(actual.value_or({})));
VERIFY_ARE_EQUAL(expected, actual, msg.c_str());
}
// 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[97u");
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");
}
};

View File

@@ -12,7 +12,6 @@
<Import Project="$(SolutionDir)src\common.nugetversions.props" />
<ItemGroup>
<ClCompile Include="..\mouseInput.cpp" />
<ClCompile Include="..\mouseInputState.cpp" />
<ClCompile Include="..\terminalInput.cpp" />
<ClCompile Include="..\precomp.cpp">
<PrecompiledHeader>Create</PrecompiledHeader>

View File

@@ -487,7 +487,7 @@ TerminalInput::OutputType TerminalInput::_GenerateSGRSequence(const til::point p
// True if the alternate buffer is active and alternate scroll mode is enabled and the event is a mouse wheel event.
bool TerminalInput::ShouldSendAlternateScroll(const unsigned int button, const short delta) const noexcept
{
const auto inAltBuffer{ _mouseInputState.inAlternateBuffer };
const auto inAltBuffer{ _inAlternateBuffer };
const auto inAltScroll{ _inputMode.test(Mode::AlternateScroll) };
const auto wasMouseWheel{ (button == WM_MOUSEWHEEL || button == WM_MOUSEHWHEEL) && delta != 0 };
return inAltBuffer && inAltScroll && wasMouseWheel;

View File

@@ -1,30 +0,0 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "precomp.h"
#include <windows.h>
#include "terminalInput.hpp"
using namespace Microsoft::Console::VirtualTerminal;
// Routine Description:
// - Notify the MouseInput handler that the screen buffer has been swapped to the alternate buffer
// Parameters:
// <none>
// Return value:
// <none>
void TerminalInput::UseAlternateScreenBuffer() noexcept
{
_mouseInputState.inAlternateBuffer = true;
}
// Routine Description:
// - Notify the MouseInput handler that the screen buffer has been swapped to the alternate buffer
// Parameters:
// <none>
// Return value:
// <none>
void TerminalInput::UseMainScreenBuffer() noexcept
{
_mouseInputState.inAlternateBuffer = false;
}

View File

@@ -30,7 +30,6 @@ PRECOMPILED_INCLUDE = ..\precomp.h
SOURCES= \
..\terminalInput.cpp \
..\mouseInput.cpp \
..\mouseInputState.cpp \
INCLUDES = \
$(INCLUDES); \

File diff suppressed because it is too large Load Diff

View File

@@ -47,26 +47,144 @@ namespace Microsoft::Console::VirtualTerminal
AlternateScroll
};
// Kitty keyboard protocol progressive enhancement flags
// https://sw.kovidgoyal.net/kitty/keyboard-protocol/
struct KittyKeyboardProtocolFlags
{
static constexpr uint8_t None = 0;
static constexpr uint8_t DisambiguateEscapeCodes = 1 << 0;
static constexpr uint8_t ReportEventTypes = 1 << 1;
static constexpr uint8_t ReportAlternateKeys = 1 << 2;
static constexpr uint8_t ReportAllKeysAsEscapeCodes = 1 << 3;
static constexpr uint8_t ReportAssociatedText = 1 << 4;
static constexpr uint8_t All = (1 << 5) - 1;
};
enum class KittyKeyboardProtocolMode : uint8_t
{
Replace = 1,
Set = 2,
Reset = 3,
};
TerminalInput() noexcept;
void SetInputMode(const Mode mode, const bool enabled) noexcept;
bool GetInputMode(const Mode mode) const noexcept;
void UseAlternateScreenBuffer() noexcept;
void UseMainScreenBuffer() noexcept;
void SetInputMode(Mode mode, bool enabled) noexcept;
bool GetInputMode(Mode mode) const noexcept;
void ResetInputModes() noexcept;
void ForceDisableWin32InputMode(const bool win32InputMode) noexcept;
void ForceDisableWin32InputMode(bool win32InputMode) noexcept;
void ForceDisableKittyKeyboardProtocol(bool disable) noexcept;
// Kitty keyboard protocol methods
void SetKittyKeyboardProtocol(uint8_t flags, KittyKeyboardProtocolMode mode) noexcept;
uint8_t GetKittyFlags() const noexcept;
void PushKittyFlags(uint8_t flags);
void PopKittyFlags(size_t count);
void ResetKittyKeyboardProtocols() noexcept;
#pragma region MouseInput
// These methods are defined in mouseInput.cpp
bool IsTrackingMouseInput() const noexcept;
bool ShouldSendAlternateScroll(const unsigned int button, const short delta) const noexcept;
#pragma endregion
#pragma region MouseInputState Management
// These methods are defined in mouseInputState.cpp
void UseAlternateScreenBuffer() noexcept;
void UseMainScreenBuffer() noexcept;
bool ShouldSendAlternateScroll(unsigned int button, short delta) const noexcept;
#pragma endregion
private:
struct CodepointBuffer
{
wchar_t buf[4];
int len;
};
struct EncodingHelper
{
explicit EncodingHelper()
{
memset(this, 0, sizeof(*this));
}
void disableCtrlAltInKeyboardState() noexcept
{
keyboardState[VK_CONTROL] = 0;
keyboardState[VK_MENU] = 0;
keyboardState[VK_LCONTROL] = 0;
keyboardState[VK_RCONTROL] = 0;
keyboardState[VK_LMENU] = 0;
keyboardState[VK_RMENU] = 0;
}
CodepointBuffer getKeyboardKey(UINT vkey, DWORD controlKeyState, HKL hkl) noexcept
{
CodepointBuffer cb;
setupKeyboardState(controlKeyState);
keyboardState[vkey] = 0x80;
cb.len = ToUnicodeEx(vkey, 0, keyboardState, cb.buf, ARRAYSIZE(cb.buf), 4, hkl);
keyboardState[vkey] = 0;
return cb;
}
HKL getKeyboardLayoutCached() noexcept
{
if (!keyboardLayoutCached)
{
keyboardLayout = getKeyboardLayout();
keyboardLayoutCached = true;
}
return keyboardLayout;
}
static HKL getKeyboardLayout() noexcept
{
// We need the current keyboard layout and state to look up the character
// that would be transmitted in that state (via the ToUnicodeEx API).
return GetKeyboardLayout(GetWindowThreadProcessId(GetForegroundWindow(), nullptr));
}
void setupKeyboardState(DWORD controlKeyState) noexcept
{
const uint8_t rightAlt = WI_IsFlagSet(controlKeyState, RIGHT_ALT_PRESSED) ? 0x80 : 0;
const uint8_t leftAlt = WI_IsFlagSet(controlKeyState, LEFT_ALT_PRESSED) ? 0x80 : 0;
const uint8_t rightCtrl = WI_IsFlagSet(controlKeyState, RIGHT_CTRL_PRESSED) ? 0x80 : 0;
const uint8_t leftCtrl = WI_IsFlagSet(controlKeyState, LEFT_CTRL_PRESSED) ? 0x80 : 0;
const uint8_t leftShift = WI_IsFlagSet(controlKeyState, SHIFT_PRESSED) ? 0x80 : 0;
const uint8_t capsLock = WI_IsFlagSet(controlKeyState, CAPSLOCK_ON) ? 0x01 : 0;
keyboardState[VK_SHIFT] = leftShift;
keyboardState[VK_CONTROL] = leftCtrl | rightCtrl;
keyboardState[VK_MENU] = leftAlt | rightAlt;
keyboardState[VK_CAPITAL] = capsLock;
keyboardState[VK_LSHIFT] = leftShift;
keyboardState[VK_LCONTROL] = leftCtrl;
keyboardState[VK_RCONTROL] = rightCtrl;
keyboardState[VK_LMENU] = leftAlt;
keyboardState[VK_RMENU] = rightAlt;
}
HKL keyboardLayout;
uint8_t keyboardState[256];
uint32_t codepointWithoutCtrlAlt;
bool keyboardLayoutCached;
// A non-zero csiFinal value indicates that this key
// should be encoded as `CSI $csiParam1 ; $csiFinal`.
wchar_t csiFinal;
// The longest sequence we currently have is Kitty's with 6 parameters:
// CSI unicode-key-code:alternate-key-code-shift:alternate-key-code-base ; modifiers:event-type ; text-as-codepoint u
// That's 6 parameters, but we can greatly simplify our logic if we just make it 3x3.
uint32_t csiParam[3][3];
// A non-zero ss3Final value indicates that this key
// should be encoded as `ESC O $ss3Final`.
wchar_t ss3Final;
// 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 plainAltPrefix;
};
// storage location for the leading surrogate of a utf-16 surrogate pair
wchar_t _leadingSurrogate = 0;
@@ -80,24 +198,40 @@ namespace Microsoft::Console::VirtualTerminal
til::enumset<Mode> _inputMode{ Mode::Ansi, Mode::AutoRepeat, Mode::AlternateScroll };
bool _forceDisableWin32InputMode{ false };
bool _inAlternateBuffer{ false };
const wchar_t* _csi = L"\x1B[";
const wchar_t* _ss3 = L"\x1BO";
// Kitty keyboard protocol state
static constexpr size_t KittyStackMaxSize = 8;
bool _forceDisableKittyKeyboardProtocol = false;
uint8_t _kittyFlags = 0;
std::vector<uint8_t> _kittyMainStack;
std::vector<uint8_t> _kittyAltStack;
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;
std::array<byte, 256> _getKeyboardState(const WORD virtualKeyCode, const DWORD controlKeyState) const;
[[nodiscard]] static wchar_t _makeCtrlChar(const wchar_t ch);
[[nodiscard]] StringType _makeCharOutput(wchar_t ch);
static std::array<byte, 256> _getKeyboardState(size_t virtualKeyCode, DWORD controlKeyState);
[[nodiscard]] static uint32_t _makeCtrlChar(uint32_t ch) noexcept;
[[nodiscard]] static StringType _makeCharOutput(uint32_t ch);
[[nodiscard]] static StringType _makeNoOutput() noexcept;
[[nodiscard]] void _escapeOutput(StringType& charSequence, const bool altIsPressed) const;
[[nodiscard]] OutputType _makeWin32Output(const KEY_EVENT_RECORD& key) const;
void _fillRegularKeyEncodingInfo(EncodingHelper& enc, const KEY_EVENT_RECORD& key, DWORD simpleKeyState) const noexcept;
static uint32_t _getKittyFunctionalKeyCode(UINT vkey, WORD scanCode, DWORD simpleKeyState) noexcept;
std::vector<uint8_t>& _getKittyStack() noexcept;
static bool _codepointIsText(uint32_t cp) noexcept;
static void _stringPushCodepoint(std::wstring& str, uint32_t cp);
static CodepointBuffer _codepointToBuffer(uint32_t cp) noexcept;
static uint32_t _bufferToCodepoint(const wchar_t* str) noexcept;
static uint32_t _codepointToLower(uint32_t cp) noexcept;
static uint32_t _bufferToLowerCodepoint(wchar_t* buf, int cap) noexcept;
static uint32_t _getBaseLayoutCodepoint(WORD scanCode) noexcept;
#pragma region MouseInputState Management
// These methods are defined in mouseInputState.cpp
struct MouseInputState
{
bool inAlternateBuffer{ false };
til::point lastPos{ -1, -1 };
unsigned int lastButton{ 0 };
int accumulatedDelta{ 0 };
@@ -113,7 +247,7 @@ namespace Microsoft::Console::VirtualTerminal
[[nodiscard]] OutputType _makeAlternateScrollOutput(unsigned int button, short delta) const;
static constexpr unsigned int s_GetPressedButton(const MouseButtonState state) noexcept;
static constexpr unsigned int s_GetPressedButton(MouseButtonState state) noexcept;
#pragma endregion
};
}

View File

@@ -546,6 +546,18 @@ bool OutputStateMachineEngine::ActionCsiDispatch(const VTID id, const VTParamete
case CsiActionCodes::ANSISYSRC_CursorRestore:
_dispatch->CursorRestoreState();
break;
case CsiActionCodes::KKP_KittyKeyboardSet:
_dispatch->SetKittyKeyboardProtocol(parameters.at(0), parameters.at(1));
break;
case CsiActionCodes::KKP_KittyKeyboardQuery:
_dispatch->QueryKittyKeyboardProtocol();
break;
case CsiActionCodes::KKP_KittyKeyboardPush:
_dispatch->PushKittyKeyboardProtocol(parameters.at(0));
break;
case CsiActionCodes::KKP_KittyKeyboardPop:
_dispatch->PopKittyKeyboardProtocol(parameters.at(0));
break;
case CsiActionCodes::IL_InsertLine:
_dispatch->InsertLine(parameters.at(0));
break;

View File

@@ -136,6 +136,10 @@ namespace Microsoft::Console::VirtualTerminal
DECSLRM_SetLeftRightMargins = VTID("s"),
DTTERM_WindowManipulation = VTID("t"), // NOTE: Overlaps with DECSLPP. Fix when/if implemented.
ANSISYSRC_CursorRestore = VTID("u"),
KKP_KittyKeyboardSet = VTID("=u"),
KKP_KittyKeyboardQuery = VTID("?u"),
KKP_KittyKeyboardPush = VTID(">u"),
KKP_KittyKeyboardPop = VTID("<u"),
DECREQTPARM_RequestTerminalParameters = VTID("x"),
PPA_PagePositionAbsolute = VTID(" P"),
PPR_PagePositionRelative = VTID(" Q"),

16
src/tools/kitty-keyboard-test/Cargo.lock generated Normal file
View File

@@ -0,0 +1,16 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "kitty-keyboard-test"
version = "0.1.0"
dependencies = [
"libc",
]
[[package]]
name = "libc"
version = "0.2.180"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc"

View File

@@ -0,0 +1,10 @@
[package]
name = "kitty-keyboard-test"
version = "0.1.0"
edition = "2021"
description = "Interactive tester for the Kitty keyboard protocol enhancement flags"
[dependencies]
[target.'cfg(unix)'.dependencies]
libc = "0.2"

View File

@@ -0,0 +1,81 @@
# Kitty Keyboard Protocol Tester
An interactive tool for testing the [Kitty keyboard protocol](https://sw.kovidgoyal.net/kitty/keyboard-protocol/) enhancement flags.
## Building
```sh
cargo build --release
```
## Usage
Run the tool in a terminal that supports the Kitty keyboard protocol:
```sh
cargo run
```
or after building:
```sh
./target/release/kitty-keyboard-test
```
## Controls
| Key | Action |
|-----|--------|
| `1` | Toggle **Disambiguate escape codes** (0b00001) |
| `2` | Toggle **Report event types** (0b00010) |
| `3` | Toggle **Report alternate keys** (0b00100) |
| `4` | Toggle **Report all keys as escape codes** (0b01000) |
| `5` | Toggle **Report associated text** (0b10000) |
| `q` or `Ctrl+C` | Quit |
## Enhancement Flags
1. **Disambiguate escape codes** (bit 0, value 1): Fixes legacy escape code ambiguities. Keys like Esc, Alt+key, Ctrl+key are reported using CSI u sequences.
2. **Report event types** (bit 1, value 2): Reports key press, repeat, and release events. Without this flag, only press events are reported.
3. **Report alternate keys** (bit 2, value 4): Reports shifted key and base layout key in addition to the main key code. Useful for shortcut matching across keyboard layouts.
4. **Report all keys as escape codes** (bit 3, value 8): Even text-producing keys (like regular letters) are reported as escape codes instead of plain text. Required for games and applications that need key events for all keys.
5. **Report associated text** (bit 4, value 16): When used with flag 4, also reports the text that the key would produce. The text is encoded as Unicode codepoints in the escape sequence.
## Output Format
For each key event, the tool displays:
- **Raw bytes**: The actual bytes received from the terminal
- **Escaped string**: A readable representation of the bytes
- **Decoded event**: Human-readable interpretation including key name, modifiers, event type, and any alternate keys or associated text
## Example Output
```
Raw: [0x1b, 0x5b, 0x97, 0x3b, 0x32, 0x3b, 0x41, 0x75] Str: "\x1b[97;2;65u"
→ Key: 'a' (97), Event: press, Modifiers: Shift, Text: "A"
```
## Protocol Reference
- Escape sequence to push keyboard mode: `CSI > flags u`
- Escape sequence to pop keyboard mode: `CSI < u`
- Key event format: `CSI keycode:shifted:base ; modifiers:event ; text u`
Modifiers are encoded as `1 + modifier_bits`:
- Shift: bit 0 (1)
- Alt: bit 1 (2)
- Ctrl: bit 2 (4)
- Super: bit 3 (8)
- Hyper: bit 4 (16)
- Meta: bit 5 (32)
- CapsLock: bit 6 (64)
- NumLock: bit 7 (128)
Event types:
- Press: 1 (default if omitted)
- Repeat: 2
- Release: 3

View File

@@ -0,0 +1,902 @@
//! Interactive tester for the Kitty Keyboard Protocol.
//!
//! This tool allows you to toggle the 5 enhancement flags and see how key events
//! are encoded by the terminal emulator.
//!
//! Shortcuts:
//! 1-5: Toggle enhancement flags
//! q/Ctrl+C: Quit
use std::fmt::Write as _;
// Enhancement flags
const FLAG_DISAMBIGUATE: u8 = 0b00001; // 1
const FLAG_EVENT_TYPES: u8 = 0b00010; // 2
const FLAG_ALTERNATE_KEYS: u8 = 0b00100; // 4
const FLAG_ALL_AS_ESCAPES: u8 = 0b01000; // 8
const FLAG_ASSOCIATED_TEXT: u8 = 0b10000; // 16
// Modifier bits (value is encoded as 1 + modifiers in the protocol)
const MOD_SHIFT: u8 = 0b00000001;
const MOD_ALT: u8 = 0b00000010;
const MOD_CTRL: u8 = 0b00000100;
const MOD_SUPER: u8 = 0b00001000;
const MOD_HYPER: u8 = 0b00010000;
const MOD_META: u8 = 0b00100000;
const MOD_CAPS_LOCK: u8 = 0b01000000;
const MOD_NUM_LOCK: u8 = 0b10000000;
fn main() {
let mut terminal = Terminal::new().expect("Failed to initialize terminal");
let mut output = String::with_capacity(4096);
// Detect if terminal supports Kitty keyboard protocol
// Send CSI ? u (query flags) followed by CSI c (DA1)
terminal.write(b"\x1b[?u\x1b[c");
let protocol_supported = detect_protocol_support(&mut terminal);
let mut flags: u8 = 0;
if protocol_supported {
write_flags(&mut output, flags);
} else {
let _ = write!(
output,
"\x1b[1;33mNote:\x1b[m Terminal does not support Kitty keyboard protocol.\r\n"
);
let _ = write!(
output,
" Key events will be shown in legacy format only.\r\n\r\n"
);
}
write_help(&mut output, protocol_supported);
terminal.write(output.as_bytes());
output.clear();
if protocol_supported {
// Push initial flags (0) onto the stack
write_push_keyboard_mode(&mut output, flags);
terminal.write(output.as_bytes());
output.clear();
}
let mut buf = [0u8; 64];
loop {
let n = terminal.read(&mut buf);
if n == 0 {
continue;
}
let input = &buf[..n];
// Ctrl+C --> Exit
if input == b"\x03" || input == b"\x1b[99;5u" {
break;
}
if protocol_supported {
let flag_to_toggle = match input {
b"1" | b"\x1b[49u" | b"\x1b[49;;49u" => Some(FLAG_DISAMBIGUATE),
b"2" | b"\x1b[50u" | b"\x1b[50;;50u" => Some(FLAG_EVENT_TYPES),
b"3" | b"\x1b[51u" | b"\x1b[51;;51u" => Some(FLAG_ALTERNATE_KEYS),
b"4" | b"\x1b[52u" | b"\x1b[52;;52u" => Some(FLAG_ALL_AS_ESCAPES),
b"5" | b"\x1b[53u" | b"\x1b[53;;53u" => Some(FLAG_ASSOCIATED_TEXT),
_ => None,
};
if let Some(flag) = flag_to_toggle {
flags ^= flag;
write_set_keyboard_mode(&mut output, flags);
write_flags(&mut output, flags);
write_help(&mut output, protocol_supported);
terminal.write(output.as_bytes());
output.clear();
continue;
}
}
write_decoded_input(&mut output, input);
terminal.write(output.as_bytes());
output.clear();
}
if protocol_supported {
write_pop_keyboard_mode(&mut output);
terminal.write(output.as_bytes());
}
}
/// Detect Kitty keyboard protocol support by looking for CSI ? <num> u response
/// before the DA1 response (CSI ... c).
fn detect_protocol_support(terminal: &mut Terminal) -> bool {
let mut buf = [0u8; 256];
let mut response = Vec::new();
let mut got_kitty_response = false;
// Read until we see the DA1 response terminator 'c'
loop {
let n = terminal.read(&mut buf);
response.extend_from_slice(&buf[..n]);
// Parse the accumulated response
let mut i = 0;
while i < response.len() {
if response[i] == 0x1b && i + 1 < response.len() && response[i + 1] == b'[' {
// Found CSI, look for the terminator
if let Some(end) = find_csi_end(&response[i + 2..]) {
let seq_end = i + 2 + end;
let params = &response[i + 2..seq_end];
let terminator = response[seq_end];
if terminator == b'u' && params.starts_with(b"?") {
// CSI ? <num> u - Kitty keyboard query response
got_kitty_response = true;
} else if terminator == b'c' {
// DA1 response - we're done
return got_kitty_response;
}
i = seq_end + 1;
continue;
}
}
i += 1;
}
}
}
/// Find the end of CSI parameters (returns index of terminator byte)
fn find_csi_end(data: &[u8]) -> Option<usize> {
for (i, &b) in data.iter().enumerate() {
// CSI terminators are in the range 0x40-0x7E
if (0x40..=0x7E).contains(&b) {
return Some(i);
}
// Parameters and intermediates are in 0x20-0x3F range
if !((0x20..=0x3F).contains(&b)) {
return None;
}
}
None
}
fn write_flags(out: &mut String, flags: u8) {
let _ = write!(out, "\x1b[1mEnhancement Flags:\x1b[m\r\n");
let _ = write!(
out,
" [{}] \x1b[33m1\x1b[m: Disambiguate escape codes (0b00001)\r\n",
if flags & FLAG_DISAMBIGUATE != 0 {
"\x1b[32m✓\x1b[m"
} else {
" "
}
);
let _ = write!(
out,
" [{}] \x1b[33m2\x1b[m: Report event types (0b00010)\r\n",
if flags & FLAG_EVENT_TYPES != 0 {
"\x1b[32m✓\x1b[m"
} else {
" "
}
);
let _ = write!(
out,
" [{}] \x1b[33m3\x1b[m: Report alternate keys (0b00100)\r\n",
if flags & FLAG_ALTERNATE_KEYS != 0 {
"\x1b[32m✓\x1b[m"
} else {
" "
}
);
let _ = write!(
out,
" [{}] \x1b[33m4\x1b[m: Report all keys as escapes (0b01000)\r\n",
if flags & FLAG_ALL_AS_ESCAPES != 0 {
"\x1b[32m✓\x1b[m"
} else {
" "
}
);
let _ = write!(
out,
" [{}] \x1b[33m5\x1b[m: Report associated text (0b10000)\r\n",
if flags & FLAG_ASSOCIATED_TEXT != 0 {
"\x1b[32m✓\x1b[m"
} else {
" "
}
);
let _ = write!(out, "\r\n");
let _ = write!(
out,
" \x1b[1mCurrent flags value:\x1b[m \x1b[36m{}\x1b[m (0b{:05b})\r\n",
flags, flags
);
let _ = write!(out, "\r\n");
}
fn write_help(out: &mut String, protocol_supported: bool) {
let _ = write!(
out,
"\x1b[90m────────────────────────────────────────────────────────\x1b[m\r\n"
);
if protocol_supported {
let _ = write!(out, "\x1b[1mControls:\x1b[m Press \x1b[33m1-5\x1b[m to toggle flags, \x1b[33mCtrl+C\x1b[m to quit\r\n");
} else {
let _ = write!(
out,
"\x1b[1mControls:\x1b[m Press \x1b[33mCtrl+C\x1b[m to quit\r\n"
);
}
let _ = write!(
out,
"\x1b[90m────────────────────────────────────────────────────────\x1b[m\r\n"
);
let _ = write!(out, "\r\n");
let _ = write!(out, "\x1b[1mKey events:\x1b[m\r\n");
let _ = write!(out, "\r\n");
}
fn write_push_keyboard_mode(out: &mut String, flags: u8) {
// CSI > flags u - Push flags onto the stack
let _ = write!(out, "\x1b[>{}u", flags);
}
fn write_set_keyboard_mode(out: &mut String, flags: u8) {
// CSI = flags ; 1 u - Set flags (mode 1 = replace all)
let _ = write!(out, "\x1b[={};1u", flags);
}
fn write_pop_keyboard_mode(out: &mut String) {
// CSI < u - Pop from the stack (restores previous mode)
let _ = write!(out, "\x1b[<u");
}
fn write_decoded_input(out: &mut String, input: &[u8]) {
let _ = write!(out, "\x1b[37m\"");
// Print as escaped string
for &b in input {
match b {
0x1b => {
let _ = write!(out, "\\x1b");
}
0x00..=0x1f => {
let _ = write!(out, "\\x{:02x}", b);
}
0x7f => {
let _ = write!(out, "\\x7f");
}
_ => {
let _ = write!(out, "{}", b as char);
}
}
}
let _ = write!(out, "\"");
// Try to decode as Kitty protocol
if let Some(decoded) = decode_kitty_sequence(input) {
let _ = write!(out, "\x1b[m\r\n \x1b[1;32m→ {}\x1b[m", decoded);
} else if let Some(decoded) = decode_legacy_sequence(input) {
let _ = write!(
out,
"\x1b[m\r\n \x1b[1;33m→ {} (legacy)\x1b[m",
decoded
);
} else if input.len() == 1 && input[0] >= 0x20 && input[0] < 0x7f {
let _ = write!(
out,
"\x1b[m\r\n \x1b[1;34m→ Character: '{}'\x1b[m",
input[0] as char
);
} else if input.len() == 1 {
if let Some(name) = control_char_name(input[0]) {
let _ = write!(out, "\x1b[m\r\n \x1b[1;35m→ {}\x1b[m", name);
}
}
let _ = write!(out, "\x1b[m\r\n");
}
fn decode_kitty_sequence(input: &[u8]) -> Option<String> {
// Check for CSI ... u format
if input.len() < 3 || input[0] != 0x1b || input[1] != b'[' {
return None;
}
let rest = &input[2..];
// CSI ? flags u - Query response
if rest.starts_with(b"?") && rest.ends_with(b"u") {
let num_str = std::str::from_utf8(&rest[1..rest.len() - 1]).ok()?;
if let Ok(flags) = num_str.parse::<u8>() {
return Some(format!("Query response: flags={} (0b{:05b})", flags, flags));
}
}
// CSI ... u - Key event
if rest.ends_with(b"u") {
return decode_csi_u_sequence(&rest[..rest.len() - 1]);
}
// CSI ... ~ - Legacy functional key with possible kitty extensions
if rest.ends_with(b"~") {
return decode_csi_tilde_sequence(&rest[..rest.len() - 1]);
}
None
}
/// Parse "modifiers:event_type" parameter, returns (modifiers, event_type)
fn parse_modifiers_and_event(mod_part: Option<&str>) -> (u8, u8) {
if let Some(mod_part) = mod_part {
let mut mod_parts = mod_part.split(':');
let mods: u8 = mod_parts
.next()
.and_then(|s| s.parse::<u8>().ok())
.unwrap_or(1)
.saturating_sub(1);
let evt: u8 = mod_parts.next().and_then(|s| s.parse().ok()).unwrap_or(1);
(mods, evt)
} else {
(0, 1)
}
}
/// Format a codepoint as a readable key name
fn format_codepoint(code: u32) -> String {
if let Some(c) = char::from_u32(code) {
if c.is_control() {
format!("{}", code)
} else {
format!("'{}' ({})", c, code)
}
} else {
format!("{}", code)
}
}
fn decode_csi_u_sequence(params: &[u8]) -> Option<String> {
let params_str = std::str::from_utf8(params).ok()?;
let mut parts = params_str.split(';');
// Parse key code (may have alternate keys separated by colons)
let key_part = parts.next()?;
let mut key_codes = key_part.split(':');
let key_code: u32 = key_codes.next()?.parse().ok()?;
let shifted_key: Option<u32> =
key_codes
.next()
.and_then(|s| if s.is_empty() { None } else { s.parse().ok() });
let base_layout_key: Option<u32> = key_codes.next().and_then(|s| s.parse().ok());
let (modifiers, event_type) = parse_modifiers_and_event(parts.next());
// Parse associated text
let text_codepoints: Option<String> = parts.next().map(|text_part| {
text_part
.split(':')
.filter_map(|s| s.parse::<u32>().ok())
.filter_map(char::from_u32)
.collect()
});
// Build result
let key_name = key_code_to_name(key_code);
let event_name = match event_type {
1 => "press",
2 => "repeat",
3 => "release",
_ => "unknown",
};
let mut result = format!("Key: {} ({}), Event: {}", key_name, key_code, event_name);
if modifiers != 0 {
result.push_str(&format!(", Modifiers: {}", modifiers_to_string(modifiers)));
}
if let Some(shifted) = shifted_key {
result.push_str(&format!(", Shifted: {}", format_codepoint(shifted)));
}
if let Some(base) = base_layout_key {
result.push_str(&format!(", Base layout: {}", format_codepoint(base)));
}
if let Some(text) = text_codepoints {
if !text.is_empty() {
result.push_str(&format!(", Text: \"{}\"", text));
}
}
Some(result)
}
fn decode_csi_tilde_sequence(params: &[u8]) -> Option<String> {
let params_str = std::str::from_utf8(params).ok()?;
let mut parts = params_str.split(';');
let key_num: u32 = parts.next()?.parse().ok()?;
let (modifiers, event_type) = parse_modifiers_and_event(parts.next());
let key_name = match key_num {
2 => "Insert",
3 => "Delete",
5 => "PageUp",
6 => "PageDown",
7 => "Home",
8 => "End",
11 => "F1",
12 => "F2",
13 => "F3",
14 => "F4",
15 => "F5",
17 => "F6",
18 => "F7",
19 => "F8",
20 => "F9",
21 => "F10",
23 => "F11",
24 => "F12",
29 => "Menu",
_ => return Some(format!("Unknown functional key: {}", key_num)),
};
let event_name = match event_type {
1 => "press",
2 => "repeat",
3 => "release",
_ => "unknown",
};
let mut result = format!("Key: {}, Event: {}", key_name, event_name);
if modifiers != 0 {
result.push_str(&format!(", Modifiers: {}", modifiers_to_string(modifiers)));
}
Some(result)
}
fn decode_legacy_sequence(input: &[u8]) -> Option<String> {
if input.len() < 2 || input[0] != 0x1b {
return None;
}
// SS3 sequences (ESC O ...)
if input.len() >= 3 && input[1] == b'O' {
let key = match input[2] {
b'A' => "Up",
b'B' => "Down",
b'C' => "Right",
b'D' => "Left",
b'H' => "Home",
b'F' => "End",
b'P' => "F1",
b'Q' => "F2",
b'R' => "F3",
b'S' => "F4",
_ => return None,
};
return Some(format!("Key: {} (SS3)", key));
}
// CSI sequences
if input.len() >= 3 && input[1] == b'[' {
let rest = &input[2..];
// CSI letter - simple cursor keys
if rest.len() == 1 {
let key = match rest[0] {
b'A' => "Up",
b'B' => "Down",
b'C' => "Right",
b'D' => "Left",
b'H' => "Home",
b'F' => "End",
b'Z' => "Shift+Tab",
_ => return None,
};
return Some(format!("Key: {}", key));
}
// CSI 1 ; modifier letter
if rest.len() >= 4 && rest[0] == b'1' && rest[1] == b';' {
if let Ok(mod_str) = std::str::from_utf8(&rest[2..rest.len() - 1]) {
if let Ok(mod_val) = mod_str.parse::<u8>() {
let modifiers = mod_val.saturating_sub(1);
let key = match rest[rest.len() - 1] {
b'A' => "Up",
b'B' => "Down",
b'C' => "Right",
b'D' => "Left",
b'H' => "Home",
b'F' => "End",
b'P' => "F1",
b'Q' => "F2",
b'S' => "F4",
_ => return None,
};
return Some(format!(
"Key: {}, Modifiers: {}",
key,
modifiers_to_string(modifiers)
));
}
}
}
}
// Alt + key
if input.len() == 2 && input[1] >= 0x20 && input[1] < 0x7f {
return Some(format!("Alt+'{}'", input[1] as char));
}
None
}
fn key_code_to_name(code: u32) -> String {
match code {
9 => "Tab".to_string(),
13 => "Enter".to_string(),
27 => "Escape".to_string(),
32 => "Space".to_string(),
127 => "Backspace".to_string(),
// Functional keys in Private Use Area
57358 => "CapsLock".to_string(),
57359 => "ScrollLock".to_string(),
57360 => "NumLock".to_string(),
57361 => "PrintScreen".to_string(),
57362 => "Pause".to_string(),
57363 => "Menu".to_string(),
57376..=57398 => format!("F{}", code - 57376 + 13), // F13-F35
57399..=57408 => format!("KP_{}", code - 57399), // KP_0 - KP_9
57409 => "KP_Decimal".to_string(),
57410 => "KP_Divide".to_string(),
57411 => "KP_Multiply".to_string(),
57412 => "KP_Subtract".to_string(),
57413 => "KP_Add".to_string(),
57414 => "KP_Enter".to_string(),
57415 => "KP_Equal".to_string(),
57416 => "KP_Separator".to_string(),
57417 => "KP_Left".to_string(),
57418 => "KP_Right".to_string(),
57419 => "KP_Up".to_string(),
57420 => "KP_Down".to_string(),
57421 => "KP_PageUp".to_string(),
57422 => "KP_PageDown".to_string(),
57423 => "KP_Home".to_string(),
57424 => "KP_End".to_string(),
57425 => "KP_Insert".to_string(),
57426 => "KP_Delete".to_string(),
57427 => "KP_Begin".to_string(),
57428 => "MediaPlay".to_string(),
57429 => "MediaPause".to_string(),
57430 => "MediaPlayPause".to_string(),
57431 => "MediaReverse".to_string(),
57432 => "MediaStop".to_string(),
57433 => "MediaFastForward".to_string(),
57434 => "MediaRewind".to_string(),
57435 => "MediaTrackNext".to_string(),
57436 => "MediaTrackPrevious".to_string(),
57437 => "MediaRecord".to_string(),
57438 => "LowerVolume".to_string(),
57439 => "RaiseVolume".to_string(),
57440 => "MuteVolume".to_string(),
57441 => "LeftShift".to_string(),
57442 => "LeftControl".to_string(),
57443 => "LeftAlt".to_string(),
57444 => "LeftSuper".to_string(),
57445 => "LeftHyper".to_string(),
57446 => "LeftMeta".to_string(),
57447 => "RightShift".to_string(),
57448 => "RightControl".to_string(),
57449 => "RightAlt".to_string(),
57450 => "RightSuper".to_string(),
57451 => "RightHyper".to_string(),
57452 => "RightMeta".to_string(),
57453 => "IsoLevel3Shift".to_string(),
57454 => "IsoLevel5Shift".to_string(),
// Regular characters
c if (0x20..0x7f).contains(&c) => format!("'{}'", char::from_u32(c).unwrap()),
c => {
if let Some(ch) = char::from_u32(c) {
format!("'{}' (U+{:04X})", ch, c)
} else {
format!("U+{:04X}", c)
}
}
}
}
fn modifiers_to_string(mods: u8) -> String {
let mut parts = Vec::new();
if mods & MOD_SHIFT != 0 {
parts.push("Shift");
}
if mods & MOD_ALT != 0 {
parts.push("Alt");
}
if mods & MOD_CTRL != 0 {
parts.push("Ctrl");
}
if mods & MOD_SUPER != 0 {
parts.push("Super");
}
if mods & MOD_HYPER != 0 {
parts.push("Hyper");
}
if mods & MOD_META != 0 {
parts.push("Meta");
}
if mods & MOD_CAPS_LOCK != 0 {
parts.push("CapsLock");
}
if mods & MOD_NUM_LOCK != 0 {
parts.push("NumLock");
}
if parts.is_empty() {
"None".to_string()
} else {
parts.join("+")
}
}
fn control_char_name(b: u8) -> Option<&'static str> {
match b {
0x00 => Some("Ctrl+Space (NUL)"),
0x01 => Some("Ctrl+A"),
0x02 => Some("Ctrl+B"),
0x03 => Some("Ctrl+C"),
0x04 => Some("Ctrl+D"),
0x05 => Some("Ctrl+E"),
0x06 => Some("Ctrl+F"),
0x07 => Some("Ctrl+G (BEL)"),
0x08 => Some("Ctrl+H (Backspace)"),
0x09 => Some("Tab"),
0x0a => Some("Ctrl+J (Line Feed)"),
0x0b => Some("Ctrl+K"),
0x0c => Some("Ctrl+L"),
0x0d => Some("Enter"),
0x0e => Some("Ctrl+N"),
0x0f => Some("Ctrl+O"),
0x10 => Some("Ctrl+P"),
0x11 => Some("Ctrl+Q"),
0x12 => Some("Ctrl+R"),
0x13 => Some("Ctrl+S"),
0x14 => Some("Ctrl+T"),
0x15 => Some("Ctrl+U"),
0x16 => Some("Ctrl+V"),
0x17 => Some("Ctrl+W"),
0x18 => Some("Ctrl+X"),
0x19 => Some("Ctrl+Y"),
0x1a => Some("Ctrl+Z"),
0x1b => Some("Escape"),
0x1c => Some("Ctrl+\\"),
0x1d => Some("Ctrl+]"),
0x1e => Some("Ctrl+^"),
0x1f => Some("Ctrl+_"),
0x7f => Some("Backspace (DEL)"),
_ => None,
}
}
// Platform-specific terminal handling
#[cfg(unix)]
mod platform {
use std::io;
use std::mem::MaybeUninit;
const STDIN_FILENO: libc::c_int = 0;
const STDOUT_FILENO: libc::c_int = 1;
pub struct Terminal {
original_termios: libc::termios,
}
impl Terminal {
pub fn new() -> io::Result<Self> {
let mut termios = MaybeUninit::uninit();
unsafe {
if libc::tcgetattr(STDIN_FILENO, termios.as_mut_ptr()) != 0 {
return Err(io::Error::last_os_error());
}
}
let original_termios = unsafe { termios.assume_init() };
let mut raw = original_termios;
// Set raw mode
raw.c_lflag &= !(libc::ECHO | libc::ICANON | libc::ISIG | libc::IEXTEN);
raw.c_iflag &= !(libc::IXON | libc::ICRNL | libc::BRKINT | libc::INPCK | libc::ISTRIP);
raw.c_oflag &= !libc::OPOST;
unsafe {
if libc::tcsetattr(STDIN_FILENO, libc::TCSAFLUSH, &raw) != 0 {
return Err(io::Error::last_os_error());
}
}
Ok(Terminal { original_termios })
}
pub fn read(&mut self, buf: &mut [u8]) -> usize {
unsafe {
let n = libc::read(
STDIN_FILENO,
buf.as_mut_ptr() as *mut libc::c_void,
buf.len(),
);
if n < 0 {
0
} else {
n as usize
}
}
}
pub fn write(&mut self, buf: &[u8]) {
unsafe {
libc::write(
STDOUT_FILENO,
buf.as_ptr() as *const libc::c_void,
buf.len(),
);
}
}
}
impl Drop for Terminal {
fn drop(&mut self) {
unsafe {
libc::tcsetattr(STDIN_FILENO, libc::TCSAFLUSH, &self.original_termios);
}
}
}
}
#[allow(non_camel_case_types, clippy::upper_case_acronyms)]
#[cfg(windows)]
mod platform {
use std::io;
type BOOL = i32;
type HANDLE = *mut core::ffi::c_void;
type CONSOLE_MODE = u32;
type STD_HANDLE = u32;
const STD_INPUT_HANDLE: STD_HANDLE = 0xFFFFFFF6;
const STD_OUTPUT_HANDLE: STD_HANDLE = 0xFFFFFFF5;
const ENABLE_PROCESSED_OUTPUT: CONSOLE_MODE = 1u32;
const ENABLE_WRAP_AT_EOL_OUTPUT: CONSOLE_MODE = 2u32;
const ENABLE_VIRTUAL_TERMINAL_PROCESSING: CONSOLE_MODE = 4u32;
const DISABLE_NEWLINE_AUTO_RETURN: CONSOLE_MODE = 8u32;
const ENABLE_VIRTUAL_TERMINAL_INPUT: CONSOLE_MODE = 512u32;
const CP_UTF8: u32 = 65001;
unsafe extern "system" {
fn ReadFile(
hfile: HANDLE,
lpbuffer: *mut u8,
nnumberofbytestoread: u32,
lpnumberofbytesread: *mut u32,
lpoverlapped: *mut (),
) -> BOOL;
fn WriteFile(
hfile: HANDLE,
lpbuffer: *const u8,
nnumberofbytestowrite: u32,
lpnumberofbyteswritten: *mut u32,
lpoverlapped: *mut (),
) -> BOOL;
fn GetStdHandle(nstdhandle: STD_HANDLE) -> HANDLE;
fn GetConsoleMode(hconsolehandle: HANDLE, lpmode: *mut CONSOLE_MODE) -> BOOL;
fn SetConsoleMode(hconsolehandle: HANDLE, dwmode: CONSOLE_MODE) -> BOOL;
fn GetConsoleCP() -> u32;
fn SetConsoleCP(wcodepageid: u32) -> BOOL;
fn GetConsoleOutputCP() -> u32;
fn SetConsoleOutputCP(wcodepageid: u32) -> BOOL;
}
pub struct Terminal {
stdin_handle: HANDLE,
stdout_handle: HANDLE,
stdin_mode: CONSOLE_MODE,
stdout_mode: CONSOLE_MODE,
stdin_cp: u32,
stdout_cp: u32,
}
impl Terminal {
pub fn new() -> io::Result<Self> {
unsafe {
let stdin_handle = GetStdHandle(STD_INPUT_HANDLE);
let stdout_handle = GetStdHandle(STD_OUTPUT_HANDLE);
let mut stdin_mode: CONSOLE_MODE = 0;
let mut stdout_mode: CONSOLE_MODE = 0;
GetConsoleMode(stdin_handle, &mut stdin_mode);
GetConsoleMode(stdin_handle, &mut stdout_mode);
SetConsoleMode(stdin_handle, ENABLE_VIRTUAL_TERMINAL_INPUT);
SetConsoleMode(
stdout_handle,
ENABLE_PROCESSED_OUTPUT
| ENABLE_WRAP_AT_EOL_OUTPUT
| ENABLE_VIRTUAL_TERMINAL_PROCESSING
| DISABLE_NEWLINE_AUTO_RETURN,
);
let stdin_cp = GetConsoleCP();
let stdout_cp = GetConsoleOutputCP();
SetConsoleCP(CP_UTF8);
SetConsoleOutputCP(CP_UTF8);
Ok(Terminal {
stdin_handle,
stdout_handle,
stdin_mode,
stdout_mode,
stdin_cp,
stdout_cp,
})
}
}
pub fn read(&mut self, buf: &mut [u8]) -> usize {
unsafe {
let mut bytes_read: u32 = 0;
if ReadFile(
self.stdin_handle,
buf.as_mut_ptr() as *mut _,
buf.len() as u32,
&mut bytes_read,
std::ptr::null_mut(),
) == 0
{
0
} else {
bytes_read as usize
}
}
}
pub fn write(&mut self, buf: &[u8]) {
unsafe {
let mut bytes_written: u32 = 0;
WriteFile(
self.stdout_handle,
buf.as_ptr() as *const _,
buf.len() as u32,
&mut bytes_written,
std::ptr::null_mut(),
);
}
}
}
impl Drop for Terminal {
fn drop(&mut self) {
unsafe {
SetConsoleMode(self.stdin_handle, self.stdin_mode);
SetConsoleMode(self.stdout_handle, self.stdout_mode);
SetConsoleCP(self.stdin_cp);
SetConsoleOutputCP(self.stdout_cp);
}
}
}
}
use platform::Terminal;

View File

@@ -0,0 +1 @@
{"rustc_fingerprint":15155536823543150984,"outputs":{"7971740275564407648":{"success":true,"status":"","code":0,"stdout":"___.exe\nlib___.rlib\n___.dll\n___.dll\n___.lib\n___.dll\nC:\\Users\\lhecker\\.rustup\\toolchains\\nightly-x86_64-pc-windows-msvc\npacked\n___\ndebug_assertions\nemscripten_wasm_eh\nfmt_debug=\"full\"\noverflow_checks\npanic=\"unwind\"\nproc_macro\nrelocation_model=\"pic\"\ntarget_abi=\"\"\ntarget_arch=\"x86_64\"\ntarget_endian=\"little\"\ntarget_env=\"msvc\"\ntarget_family=\"windows\"\ntarget_feature=\"cmpxchg16b\"\ntarget_feature=\"fxsr\"\ntarget_feature=\"lahfsahf\"\ntarget_feature=\"sse\"\ntarget_feature=\"sse2\"\ntarget_feature=\"sse3\"\ntarget_feature=\"x87\"\ntarget_has_atomic\ntarget_has_atomic=\"128\"\ntarget_has_atomic=\"16\"\ntarget_has_atomic=\"32\"\ntarget_has_atomic=\"64\"\ntarget_has_atomic=\"8\"\ntarget_has_atomic=\"ptr\"\ntarget_has_atomic_equal_alignment=\"128\"\ntarget_has_atomic_equal_alignment=\"16\"\ntarget_has_atomic_equal_alignment=\"32\"\ntarget_has_atomic_equal_alignment=\"64\"\ntarget_has_atomic_equal_alignment=\"8\"\ntarget_has_atomic_equal_alignment=\"ptr\"\ntarget_has_atomic_load_store\ntarget_has_atomic_load_store=\"128\"\ntarget_has_atomic_load_store=\"16\"\ntarget_has_atomic_load_store=\"32\"\ntarget_has_atomic_load_store=\"64\"\ntarget_has_atomic_load_store=\"8\"\ntarget_has_atomic_load_store=\"ptr\"\ntarget_has_reliable_f128\ntarget_has_reliable_f16\ntarget_has_reliable_f16_math\ntarget_os=\"windows\"\ntarget_pointer_width=\"64\"\ntarget_thread_local\ntarget_vendor=\"pc\"\nub_checks\nwindows\n","stderr":""},"17747080675513052775":{"success":true,"status":"","code":0,"stdout":"rustc 1.95.0-nightly (873d4682c 2026-01-25)\nbinary: rustc\ncommit-hash: 873d4682c7d285540b8f28bfe637006cef8918a6\ncommit-date: 2026-01-25\nhost: x86_64-pc-windows-msvc\nrelease: 1.95.0-nightly\nLLVM version: 21.1.8\n","stderr":""}},"successes":{}}

View File

@@ -0,0 +1,3 @@
Signature: 8a477f597d28d172789f06886806bc55
# This file is a cache directory tag created by cargo.
# For information about cache directory tags see https://bford.info/cachedir/

View File

@@ -0,0 +1,12 @@
0.008865700s INFO prepare_target{force=false package_id=kitty-keyboard-test v0.1.0 (C:\Users\lhecker\projects\terminal\src\tools\kitty-keyboard-test) target="kitty-keyboard-test"}: cargo::core::compiler::fingerprint: stale: changed "C:\\Users\\lhecker\\projects\\terminal\\src\\tools\\kitty-keyboard-test\\src\\main.rs"
0.008885600s INFO prepare_target{force=false package_id=kitty-keyboard-test v0.1.0 (C:\Users\lhecker\projects\terminal\src\tools\kitty-keyboard-test) target="kitty-keyboard-test"}: cargo::core::compiler::fingerprint: (vs) "C:\\Users\\lhecker\\projects\\terminal\\src\\tools\\kitty-keyboard-test\\target\\debug\\.fingerprint\\kitty-keyboard-test-540f39a9b0e2080c\\dep-bin-kitty-keyboard-test"
0.008890400s INFO prepare_target{force=false package_id=kitty-keyboard-test v0.1.0 (C:\Users\lhecker\projects\terminal\src\tools\kitty-keyboard-test) target="kitty-keyboard-test"}: cargo::core::compiler::fingerprint: FileTime { seconds: 13414356040, nanos: 415027200 } < FileTime { seconds: 13414356217, nanos: 99954000 }
0.009009300s INFO prepare_target{force=false package_id=kitty-keyboard-test v0.1.0 (C:\Users\lhecker\projects\terminal\src\tools\kitty-keyboard-test) target="kitty-keyboard-test"}: cargo::core::compiler::fingerprint: fingerprint dirty for kitty-keyboard-test v0.1.0 (C:\Users\lhecker\projects\terminal\src\tools\kitty-keyboard-test)/Check { test: false }/TargetInner { name: "kitty-keyboard-test", doc: true, ..: with_path("C:\\Users\\lhecker\\projects\\terminal\\src\\tools\\kitty-keyboard-test\\src\\main.rs", Edition2021) }
0.009027100s INFO prepare_target{force=false package_id=kitty-keyboard-test v0.1.0 (C:\Users\lhecker\projects\terminal\src\tools\kitty-keyboard-test) target="kitty-keyboard-test"}: cargo::core::compiler::fingerprint: dirty: FsStatusOutdated(StaleItem(ChangedFile { reference: "C:\\Users\\lhecker\\projects\\terminal\\src\\tools\\kitty-keyboard-test\\target\\debug\\.fingerprint\\kitty-keyboard-test-540f39a9b0e2080c\\dep-bin-kitty-keyboard-test", reference_mtime: FileTime { seconds: 13414356040, nanos: 415027200 }, stale: "C:\\Users\\lhecker\\projects\\terminal\\src\\tools\\kitty-keyboard-test\\src\\main.rs", stale_mtime: FileTime { seconds: 13414356217, nanos: 99954000 } }))
0.009491200s INFO prepare_target{force=false package_id=kitty-keyboard-test v0.1.0 (C:\Users\lhecker\projects\terminal\src\tools\kitty-keyboard-test) target="kitty-keyboard-test"}: cargo::core::compiler::fingerprint: stale: changed "C:\\Users\\lhecker\\projects\\terminal\\src\\tools\\kitty-keyboard-test\\src\\main.rs"
0.009498900s INFO prepare_target{force=false package_id=kitty-keyboard-test v0.1.0 (C:\Users\lhecker\projects\terminal\src\tools\kitty-keyboard-test) target="kitty-keyboard-test"}: cargo::core::compiler::fingerprint: (vs) "C:\\Users\\lhecker\\projects\\terminal\\src\\tools\\kitty-keyboard-test\\target\\debug\\.fingerprint\\kitty-keyboard-test-bf4513f9cae5b3d2\\dep-test-bin-kitty-keyboard-test"
0.009502500s INFO prepare_target{force=false package_id=kitty-keyboard-test v0.1.0 (C:\Users\lhecker\projects\terminal\src\tools\kitty-keyboard-test) target="kitty-keyboard-test"}: cargo::core::compiler::fingerprint: FileTime { seconds: 13414356040, nanos: 415027200 } < FileTime { seconds: 13414356217, nanos: 99954000 }
0.009662200s INFO prepare_target{force=false package_id=kitty-keyboard-test v0.1.0 (C:\Users\lhecker\projects\terminal\src\tools\kitty-keyboard-test) target="kitty-keyboard-test"}: cargo::core::compiler::fingerprint: fingerprint dirty for kitty-keyboard-test v0.1.0 (C:\Users\lhecker\projects\terminal\src\tools\kitty-keyboard-test)/Check { test: true }/TargetInner { name: "kitty-keyboard-test", doc: true, ..: with_path("C:\\Users\\lhecker\\projects\\terminal\\src\\tools\\kitty-keyboard-test\\src\\main.rs", Edition2021) }
0.009677900s INFO prepare_target{force=false package_id=kitty-keyboard-test v0.1.0 (C:\Users\lhecker\projects\terminal\src\tools\kitty-keyboard-test) target="kitty-keyboard-test"}: cargo::core::compiler::fingerprint: dirty: FsStatusOutdated(StaleItem(ChangedFile { reference: "C:\\Users\\lhecker\\projects\\terminal\\src\\tools\\kitty-keyboard-test\\target\\debug\\.fingerprint\\kitty-keyboard-test-bf4513f9cae5b3d2\\dep-test-bin-kitty-keyboard-test", reference_mtime: FileTime { seconds: 13414356040, nanos: 415027200 }, stale: "C:\\Users\\lhecker\\projects\\terminal\\src\\tools\\kitty-keyboard-test\\src\\main.rs", stale_mtime: FileTime { seconds: 13414356217, nanos: 99954000 } }))
Checking kitty-keyboard-test v0.1.0 (C:\Users\lhecker\projects\terminal\src\tools\kitty-keyboard-test)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.08s

View File

@@ -0,0 +1,3 @@
{"reason":"compiler-artifact","package_id":"path+file:///C:/Users/lhecker/projects/terminal/src/tools/kitty-keyboard-test#0.1.0","manifest_path":"C:\\Users\\lhecker\\projects\\terminal\\src\\tools\\kitty-keyboard-test\\Cargo.toml","target":{"kind":["bin"],"crate_types":["bin"],"name":"kitty-keyboard-test","src_path":"C:\\Users\\lhecker\\projects\\terminal\\src\\tools\\kitty-keyboard-test\\src\\main.rs","edition":"2021","doc":true,"doctest":false,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":[],"filenames":["C:\\Users\\lhecker\\projects\\terminal\\src\\tools\\kitty-keyboard-test\\target\\debug\\deps\\libkitty_keyboard_test-540f39a9b0e2080c.rmeta"],"executable":null,"fresh":false}
{"reason":"compiler-artifact","package_id":"path+file:///C:/Users/lhecker/projects/terminal/src/tools/kitty-keyboard-test#0.1.0","manifest_path":"C:\\Users\\lhecker\\projects\\terminal\\src\\tools\\kitty-keyboard-test\\Cargo.toml","target":{"kind":["bin"],"crate_types":["bin"],"name":"kitty-keyboard-test","src_path":"C:\\Users\\lhecker\\projects\\terminal\\src\\tools\\kitty-keyboard-test\\src\\main.rs","edition":"2021","doc":true,"doctest":false,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":true},"features":[],"filenames":["C:\\Users\\lhecker\\projects\\terminal\\src\\tools\\kitty-keyboard-test\\target\\debug\\deps\\libkitty_keyboard_test-bf4513f9cae5b3d2.rmeta"],"executable":null,"fresh":false}
{"reason":"build-finished","success":true}