mirror of
https://github.com/microsoft/terminal.git
synced 2026-05-17 15:36:35 +00:00
Compare commits
8 Commits
dev/cazamo
...
dev/lhecke
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
940e8d5c2a | ||
|
|
173f897751 | ||
|
|
d875ff4d6c | ||
|
|
ad501c9d92 | ||
|
|
cb88820fa6 | ||
|
|
a65bff17d8 | ||
|
|
5d0e8c238c | ||
|
|
07792774f6 |
2
.github/actions/spelling/expect/expect.txt
vendored
2
.github/actions/spelling/expect/expect.txt
vendored
@@ -866,6 +866,7 @@ KILLACTIVE
|
||||
KILLFOCUS
|
||||
kinda
|
||||
KIYEOK
|
||||
KKP
|
||||
KLF
|
||||
KLMNO
|
||||
KOK
|
||||
@@ -885,6 +886,7 @@ LBUTTONDOWN
|
||||
LBUTTONUP
|
||||
lcb
|
||||
lci
|
||||
LCMAP
|
||||
LCONTROL
|
||||
LCTRL
|
||||
lcx
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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; };
|
||||
|
||||
@@ -98,6 +98,7 @@ void Terminal::UpdateSettings(ICoreSettings settings)
|
||||
}
|
||||
|
||||
_getTerminalInput().ForceDisableWin32InputMode(settings.ForceVTInput());
|
||||
_getTerminalInput().ForceDisableKittyKeyboardProtocol(!settings.AllowKittyKeyboardMode());
|
||||
|
||||
if (settings.TabColor() == nullptr)
|
||||
{
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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}"
|
||||
|
||||
@@ -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:\ -> /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:\ -> /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:\ -> /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:\ -> 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">
|
||||
|
||||
@@ -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) \
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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) \
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
721
src/terminal/adapter/ut_adapter/kittyKeyboardProtocol.cpp
Normal file
721
src/terminal/adapter/ut_adapter/kittyKeyboardProtocol.cpp
Normal 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");
|
||||
}
|
||||
};
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
@@ -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
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
16
src/tools/kitty-keyboard-test/Cargo.lock
generated
Normal 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"
|
||||
10
src/tools/kitty-keyboard-test/Cargo.toml
Normal file
10
src/tools/kitty-keyboard-test/Cargo.toml
Normal 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"
|
||||
81
src/tools/kitty-keyboard-test/README.md
Normal file
81
src/tools/kitty-keyboard-test/README.md
Normal 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
|
||||
902
src/tools/kitty-keyboard-test/src/main.rs
Normal file
902
src/tools/kitty-keyboard-test/src/main.rs
Normal 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;
|
||||
1
src/tools/kitty-keyboard-test/target/.rustc_info.json
Normal file
1
src/tools/kitty-keyboard-test/target/.rustc_info.json
Normal 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":{}}
|
||||
3
src/tools/kitty-keyboard-test/target/CACHEDIR.TAG
Normal file
3
src/tools/kitty-keyboard-test/target/CACHEDIR.TAG
Normal 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/
|
||||
12
src/tools/kitty-keyboard-test/target/flycheck0/stderr
Normal file
12
src/tools/kitty-keyboard-test/target/flycheck0/stderr
Normal 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
|
||||
3
src/tools/kitty-keyboard-test/target/flycheck0/stdout
Normal file
3
src/tools/kitty-keyboard-test/target/flycheck0/stdout
Normal 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}
|
||||
Reference in New Issue
Block a user