mirror of
https://github.com/microsoft/terminal.git
synced 2026-05-17 15:36:35 +00:00
wip
This commit is contained in:
@@ -29,14 +29,28 @@ TerminalInput::TerminalInput() noexcept
|
||||
_initKeyboardMap();
|
||||
}
|
||||
|
||||
void TerminalInput::UseAlternateScreenBuffer() noexcept
|
||||
{
|
||||
_inAlternateBuffer = true;
|
||||
}
|
||||
|
||||
void TerminalInput::UseMainScreenBuffer() noexcept
|
||||
{
|
||||
if (!_inAlternateBuffer)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_inAlternateBuffer = false;
|
||||
_kittyAltStack.clear();
|
||||
_kittyFlags = _kittyMainStack.empty() ? 0 : _kittyMainStack.back();
|
||||
}
|
||||
|
||||
void TerminalInput::UseAlternateScreenBuffer() noexcept
|
||||
{
|
||||
if (_inAlternateBuffer)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_inAlternateBuffer = true;
|
||||
_kittyAltStack.clear();
|
||||
_kittyFlags = 0;
|
||||
}
|
||||
|
||||
void TerminalInput::SetInputMode(const Mode mode, const bool enabled) noexcept
|
||||
@@ -93,6 +107,8 @@ void TerminalInput::ForceDisableKittyKeyboardProtocol(const bool disable) noexce
|
||||
if (disable)
|
||||
{
|
||||
_kittyFlags = 0;
|
||||
_kittyMainStack.clear();
|
||||
_kittyAltStack.clear();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -132,7 +148,7 @@ void TerminalInput::PushKittyFlags(const uint8_t flags) noexcept
|
||||
}
|
||||
|
||||
auto& stack = _getKittyStack();
|
||||
// Evict oldest entry if stack is full (DoS prevention)
|
||||
// > If a push request is received and the stack is full, the oldest entry from the stack must be evicted.
|
||||
if (stack.size() >= KittyStackMaxSize)
|
||||
{
|
||||
stack.erase(stack.begin());
|
||||
@@ -141,22 +157,26 @@ void TerminalInput::PushKittyFlags(const uint8_t flags) noexcept
|
||||
_kittyFlags = flags & KittyKeyboardProtocolFlags::All;
|
||||
}
|
||||
|
||||
void TerminalInput::PopKittyFlags(const size_t count) noexcept
|
||||
void TerminalInput::PopKittyFlags(size_t count) noexcept
|
||||
{
|
||||
auto& stack = _getKittyStack();
|
||||
// If pop request exceeds stack size, reset all flags per spec:
|
||||
// "If a pop request is received that empties the stack, all flags are reset."
|
||||
if (count > stack.size())
|
||||
// NOTE: It's not just an optimization to return early here.
|
||||
if (count == 0)
|
||||
{
|
||||
stack.clear();
|
||||
_kittyFlags = 0;
|
||||
return;
|
||||
}
|
||||
// Pop the requested number of entries, restoring flags from last popped
|
||||
for (size_t i = 0; i < count; ++i)
|
||||
|
||||
auto& stack = _getKittyStack();
|
||||
|
||||
if (count >= stack.size())
|
||||
{
|
||||
_kittyFlags = stack.back();
|
||||
stack.pop_back();
|
||||
// > If a pop request is received that empties the stack, all flags are reset.
|
||||
_kittyFlags = 0;
|
||||
stack.clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
_kittyFlags = stack.at(stack.size() - count);
|
||||
stack.erase(stack.end() - count, stack.end());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -204,26 +224,27 @@ TerminalInput::OutputType TerminalInput::HandleKey(const INPUT_RECORD& event)
|
||||
// GH#4999 - If we're in win32-input mode, skip straight to doing that.
|
||||
// Since this mode handles all types of key events, do nothing else.
|
||||
// Only do this if win32-input-mode support isn't manually disabled.
|
||||
if (_inputMode.test(Mode::Win32) && !_forceDisableWin32InputMode)
|
||||
if (_inputMode.test(Mode::Win32) && !_forceDisableWin32InputMode && !_kittyFlags)
|
||||
{
|
||||
return _makeWin32Output(keyEvent);
|
||||
}
|
||||
|
||||
const auto controlKeyState = _trackControlKeyState(keyEvent);
|
||||
|
||||
if (_kittyFlags)
|
||||
{
|
||||
if (auto ret = _makeKittyOutput(keyEvent, controlKeyState))
|
||||
{
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
const auto virtualKeyCode = keyEvent.wVirtualKeyCode;
|
||||
auto unicodeChar = keyEvent.uChar.UnicodeChar;
|
||||
|
||||
// Check if this key matches the last recorded key code.
|
||||
const auto matchingLastKeyPress = _lastVirtualKeyCode == virtualKeyCode;
|
||||
|
||||
// If kitty keyboard mode is active, use kitty keyboard protocol.
|
||||
// This handles release events when ReportEventTypes flag is set.
|
||||
if (_kittyFlags != 0)
|
||||
{
|
||||
return _makeKittyOutput(keyEvent, controlKeyState);
|
||||
}
|
||||
|
||||
// Only need to handle key down. See raw key handler (see RawReadWaitRoutine in stream.cpp)
|
||||
if (!keyEvent.bKeyDown)
|
||||
{
|
||||
@@ -664,7 +685,7 @@ DWORD TerminalInput::_trackControlKeyState(const KEY_EVENT_RECORD& key) noexcept
|
||||
// recent key press and associated control key state (which is all we need for
|
||||
// our ToUnicodeEx queries). This is a substitute for the GetKeyboardState API,
|
||||
// which can't be used when serving as a conpty host.
|
||||
std::array<byte, 256> TerminalInput::_getKeyboardState(const WORD virtualKeyCode, const DWORD controlKeyState) const
|
||||
std::array<byte, 256> TerminalInput::_getKeyboardState(const WORD virtualKeyCode, const DWORD controlKeyState)
|
||||
{
|
||||
auto keyState = std::array<byte, 256>{};
|
||||
if (virtualKeyCode < keyState.size())
|
||||
@@ -768,414 +789,359 @@ TerminalInput::OutputType TerminalInput::_makeWin32Output(const KEY_EVENT_RECORD
|
||||
}
|
||||
|
||||
// Generates kitty keyboard protocol output for a key event.
|
||||
// https://sw.kovidgoyal.net/kitty/keyboard-protocol/
|
||||
// See: https://sw.kovidgoyal.net/kitty/keyboard-protocol/
|
||||
//
|
||||
// Like all Kitty protocol specifications at the time (2026-01-30), it is unfortunately
|
||||
// defined in a very informal, and sometimes contradictory, narrative specification.
|
||||
// As such, you'll find that I've heavily "[editorialized]" most citations below.
|
||||
// It's still a hard read.
|
||||
TerminalInput::OutputType TerminalInput::_makeKittyOutput(const KEY_EVENT_RECORD& key, const DWORD controlKeyState)
|
||||
{
|
||||
const auto virtualKeyCode = key.wVirtualKeyCode;
|
||||
const auto virtualScanCode = key.wVirtualScanCode;
|
||||
const auto unicodeChar = key.uChar.UnicodeChar;
|
||||
const auto isKeyDown = key.bKeyDown;
|
||||
|
||||
// Swallow lone leading surrogates...
|
||||
if (til::is_leading_surrogate(unicodeChar))
|
||||
{
|
||||
_leadingSurrogate = unicodeChar;
|
||||
return _makeNoOutput();
|
||||
}
|
||||
|
||||
// ...and combine them with trailing surrogates.
|
||||
uint32_t fullCodepoint = unicodeChar;
|
||||
if (_leadingSurrogate != 0 && til::is_trailing_surrogate(unicodeChar))
|
||||
{
|
||||
fullCodepoint = til::combine_surrogates(_leadingSurrogate, unicodeChar);
|
||||
_leadingSurrogate = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
_leadingSurrogate = 0;
|
||||
}
|
||||
|
||||
// Check if this key matches the last recorded key code (for repeat detection)
|
||||
uint32_t codepoint = unicodeChar;
|
||||
const auto isRepeat = _lastVirtualKeyCode == virtualKeyCode && isKeyDown;
|
||||
if (!isKeyDown)
|
||||
|
||||
// First off, some state tracking.
|
||||
{
|
||||
if (_lastVirtualKeyCode == virtualKeyCode)
|
||||
// Swallow lone leading surrogates...
|
||||
if (til::is_leading_surrogate(unicodeChar))
|
||||
{
|
||||
_leadingSurrogate = unicodeChar;
|
||||
return _makeNoOutput();
|
||||
}
|
||||
// ...and combine them with trailing surrogates.
|
||||
if (_leadingSurrogate != 0 && til::is_trailing_surrogate(unicodeChar))
|
||||
{
|
||||
codepoint = til::combine_surrogates(_leadingSurrogate, unicodeChar);
|
||||
}
|
||||
_leadingSurrogate = 0;
|
||||
|
||||
// Track key state for key repeat detection
|
||||
if (isKeyDown)
|
||||
{
|
||||
_lastVirtualKeyCode = virtualKeyCode;
|
||||
}
|
||||
else if (_lastVirtualKeyCode == virtualKeyCode)
|
||||
{
|
||||
_lastVirtualKeyCode = std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
const bool shiftPressed = WI_IsFlagSet(controlKeyState, SHIFT_PRESSED);
|
||||
const bool ctrlPressed = WI_IsAnyFlagSet(controlKeyState, CTRL_PRESSED);
|
||||
const bool altPressed = WI_IsAnyFlagSet(controlKeyState, ALT_PRESSED);
|
||||
|
||||
// Variables for the legacy encoding (CSI [~ABCDEFHPQS])
|
||||
wchar_t legacyFinal = 0;
|
||||
int32_t legacyParam = 0;
|
||||
|
||||
// Variables for the kitty encoding (CSI u)
|
||||
// There will be a summary description of these parameters at the end,
|
||||
// where I'm building the final escape sequence string.
|
||||
int32_t kittyKeyCode = 0;
|
||||
int32_t kittyAltKeyCodeShifted = 0;
|
||||
int32_t kittyAltKeyCodeBase = 0;
|
||||
int32_t kittyModifiers = 0;
|
||||
int32_t kittyEventType = 0;
|
||||
int32_t kittyTextAsCodepoint = 0;
|
||||
|
||||
if (WI_IsFlagSet(_kittyFlags, KittyKeyboardProtocolFlags::DisambiguateEscapeCodes))
|
||||
{
|
||||
// > Turning on this flag will cause the terminal to report the
|
||||
// > [KEYS] keys using CSI u sequences instead of legacy ones.
|
||||
|
||||
// > The only exceptions [from the Disambiguate Escape Codes specification]
|
||||
// > are the Enter, Tab and Backspace keys which [...] [use] legacy mode [encoding] [...].
|
||||
if ((virtualKeyCode == VK_RETURN || virtualKeyCode == VK_TAB || virtualKeyCode == VK_BACK) &&
|
||||
WI_IsFlagClear(_kittyFlags, KittyKeyboardProtocolFlags::ReportAllKeysAsEscapeCodes))
|
||||
{
|
||||
return MakeUnhandled();
|
||||
}
|
||||
|
||||
// > Additionally, all non text keypad keys will be reported [...] with CSI u encoding, [...].
|
||||
if (codepoint == 0 && virtualKeyCode >= VK_NUMPAD0 && virtualKeyCode <= VK_DIVIDE)
|
||||
{
|
||||
kittyKeyCode = _getKittyKeyCode(key, controlKeyState);
|
||||
}
|
||||
// Where [KEYS] mentions:
|
||||
// > ESC
|
||||
else if (virtualKeyCode == VK_ESCAPE)
|
||||
{
|
||||
kittyKeyCode = 27; // ESCAPE
|
||||
}
|
||||
// Where [KEYS] mentions:
|
||||
// > alt+key, ctrl+key, ctrl+alt+key, shift+alt+key
|
||||
//
|
||||
// > Here key is any ASCII key as described in Legacy text keys. [...]
|
||||
//
|
||||
// > Legacy text keys:
|
||||
// > For legacy compatibility, the keys a-z 0-9 ` - = [ ] \ ; ' , . / [...]
|
||||
else if (controlKeyState == ALT_PRESSED ||
|
||||
controlKeyState == CTRL_PRESSED ||
|
||||
controlKeyState == (CTRL_PRESSED | ALT_PRESSED) ||
|
||||
controlKeyState == (SHIFT_PRESSED | ALT_PRESSED))
|
||||
{
|
||||
const auto hkl = GetKeyboardLayout(GetWindowThreadProcessId(GetForegroundWindow(), nullptr));
|
||||
const auto ch = MapVirtualKeyExW(virtualKeyCode, MAPVK_VK_TO_CHAR, hkl);
|
||||
if ((ch >= '`' && ch <= 'z') || // ` a-z
|
||||
(ch >= '0' && ch <= '9') ||
|
||||
(ch >= ',' && ch <= '.') || // , - .
|
||||
(ch >= '[' && ch <= ']') || // [ \ ]
|
||||
ch == L'=' || ch == L';' || ch == L'\'' || ch == L'/')
|
||||
{
|
||||
kittyKeyCode = ch;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (WI_IsFlagSet(_kittyFlags, KittyKeyboardProtocolFlags::ReportEventTypes))
|
||||
{
|
||||
// > This [...] causes the terminal to report key repeat and key release events.
|
||||
if (!isKeyDown)
|
||||
{
|
||||
kittyEventType = 3; // release event
|
||||
}
|
||||
else if (isRepeat)
|
||||
{
|
||||
kittyEventType = 2; // repeat event
|
||||
}
|
||||
else
|
||||
{
|
||||
kittyEventType = 1; // press event
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_lastVirtualKeyCode = virtualKeyCode;
|
||||
}
|
||||
|
||||
// Note: Disambiguate flag (0x01) is implicitly handled - if we're in this function
|
||||
// at all (_kittyFlags != 0), then Ctrl+key and Alt+key combos get CSI u encoding.
|
||||
const auto reportEventTypes = (_kittyFlags & KittyKeyboardProtocolFlags::ReportEventTypes) != 0;
|
||||
const auto reportAllKeys = (_kittyFlags & KittyKeyboardProtocolFlags::ReportAllKeys) != 0;
|
||||
const auto reportAlternateKeys = (_kittyFlags & KittyKeyboardProtocolFlags::ReportAlternateKeys) != 0;
|
||||
const auto reportText = (_kittyFlags & KittyKeyboardProtocolFlags::ReportText) != 0;
|
||||
|
||||
// Without ReportEventTypes, we only handle key down events
|
||||
if (!isKeyDown && !reportEventTypes)
|
||||
{
|
||||
return _makeNoOutput();
|
||||
}
|
||||
|
||||
// Get the functional key code, or 0 if this key should use legacy encoding.
|
||||
const auto functionalKeyCode = _getKittyFunctionalKeyCode(virtualKeyCode, virtualScanCode, controlKeyState);
|
||||
const auto ctrlIsPressed = WI_IsAnyFlagSet(controlKeyState, CTRL_PRESSED);
|
||||
const auto altIsPressed = WI_IsAnyFlagSet(controlKeyState, ALT_PRESSED);
|
||||
|
||||
if (!reportAllKeys)
|
||||
{
|
||||
// Per spec: "Additionally, with this mode [ReportAllKeys], events for pressing
|
||||
// modifier keys are reported." So we skip modifier key events without it.
|
||||
if ((functionalKeyCode >= 57358 && functionalKeyCode <= 57360) ||
|
||||
(functionalKeyCode >= 57441 && functionalKeyCode <= 57450))
|
||||
// > Normally only key press events are reported [...].
|
||||
if (!isKeyDown)
|
||||
{
|
||||
return _makeNoOutput();
|
||||
}
|
||||
|
||||
// Legacy encoding for Enter, Tab, and Backspace (spec recovery guarantee).
|
||||
// These keys use mode-aware legacy sequences unless ReportAllKeys is set, ensuring
|
||||
// users can type "reset<Enter>" if an app crashes with the protocol enabled.
|
||||
// Unlike CSI u (which is mode-independent), legacy encoding must honor LineFeed
|
||||
// and BackarrowKey modes. Ctrl/Alt combos still use CSI u for disambiguation.
|
||||
if (virtualKeyCode == VK_RETURN || virtualKeyCode == VK_TAB || virtualKeyCode == VK_BACK)
|
||||
{
|
||||
if (!isKeyDown || ctrlIsPressed || altIsPressed)
|
||||
{
|
||||
return _makeNoOutput();
|
||||
}
|
||||
|
||||
std::wstring str;
|
||||
switch (virtualKeyCode)
|
||||
{
|
||||
case VK_RETURN:
|
||||
str = _inputMode.test(Mode::LineFeed) ? L"\r\n" : L"\r";
|
||||
break;
|
||||
case VK_TAB:
|
||||
if (WI_IsFlagSet(controlKeyState, SHIFT_PRESSED))
|
||||
{
|
||||
str = fmt::format(FMT_COMPILE(L"{}Z"), _csi);
|
||||
}
|
||||
else
|
||||
{
|
||||
str = L"\t";
|
||||
}
|
||||
break;
|
||||
case VK_BACK:
|
||||
str = _inputMode.test(Mode::BackarrowKey) ? L"\x08" : L"\x7f";
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return MakeOutput(std::move(str));
|
||||
}
|
||||
|
||||
// Fast path: For simple text key presses (key down, not a functional key, has a codepoint),
|
||||
// without Ctrl/Alt modifiers that require disambiguation, and not in reportAllKeys mode,
|
||||
// we can bypass CSI u encoding and send the character directly.
|
||||
if (isKeyDown && functionalKeyCode == 0 && fullCodepoint != 0 && !ctrlIsPressed && !altIsPressed)
|
||||
{
|
||||
const auto cb = _codepointToBuffer(fullCodepoint);
|
||||
return MakeOutput({ cb.buf, cb.len });
|
||||
}
|
||||
}
|
||||
|
||||
const auto isEnhanced = WI_IsFlagSet(controlKeyState, ENHANCED_KEY);
|
||||
wchar_t legacyFinalChar = 0;
|
||||
uint32_t legacyParam = 1;
|
||||
|
||||
switch (virtualKeyCode)
|
||||
if (WI_IsFlagSet(_kittyFlags, KittyKeyboardProtocolFlags::ReportAllKeysAsEscapeCodes))
|
||||
{
|
||||
case VK_UP:
|
||||
if (isEnhanced)
|
||||
// > This [...] turns on key reporting even for key events that generate text.
|
||||
// > [...] text will not be sent, instead only key events are sent.
|
||||
//
|
||||
// > [...] with this mode, events for pressing modifier keys are reported.
|
||||
|
||||
if (WI_IsFlagSet(_kittyFlags, KittyKeyboardProtocolFlags::ReportAssociatedText))
|
||||
{
|
||||
legacyFinalChar = L'A';
|
||||
// > This [...] causes key events that generate text to be reported
|
||||
// > as CSI u escape codes with the text embedded in the escape code.
|
||||
//
|
||||
// > Note that this flag is an enhancement to Report all keys as escape codes [...].
|
||||
kittyTextAsCodepoint = codepoint;
|
||||
}
|
||||
break;
|
||||
case VK_DOWN:
|
||||
if (isEnhanced)
|
||||
}
|
||||
else
|
||||
{
|
||||
// The inverse of reporting all keys is that we don't handle keys that we haven't yet handled.
|
||||
if (kittyKeyCode == 0)
|
||||
{
|
||||
legacyFinalChar = L'B';
|
||||
return MakeUnhandled();
|
||||
}
|
||||
break;
|
||||
case VK_RIGHT:
|
||||
if (isEnhanced)
|
||||
|
||||
// From a side note (?!) on the "Report event types" section:
|
||||
//
|
||||
// > NOTE: The Enter, Tab and Backspace keys will not have release
|
||||
// > events unless Report all keys as escape codes is also set [...]
|
||||
if (kittyEventType == 3 && (virtualKeyCode == VK_RETURN || virtualKeyCode == VK_TAB || virtualKeyCode == VK_BACK))
|
||||
{
|
||||
legacyFinalChar = L'C';
|
||||
return _makeNoOutput();
|
||||
}
|
||||
break;
|
||||
case VK_LEFT:
|
||||
if (isEnhanced)
|
||||
{
|
||||
legacyFinalChar = L'D';
|
||||
}
|
||||
break;
|
||||
case VK_HOME:
|
||||
if (isEnhanced)
|
||||
{
|
||||
legacyFinalChar = L'H';
|
||||
}
|
||||
break;
|
||||
case VK_END:
|
||||
if (isEnhanced)
|
||||
{
|
||||
legacyFinalChar = L'F';
|
||||
}
|
||||
break;
|
||||
case VK_INSERT:
|
||||
case VK_DELETE:
|
||||
if (isEnhanced)
|
||||
{
|
||||
legacyFinalChar = L'~';
|
||||
legacyParam = 2 + (virtualKeyCode - VK_INSERT);
|
||||
}
|
||||
break;
|
||||
case VK_PRIOR:
|
||||
case VK_NEXT:
|
||||
if (isEnhanced)
|
||||
{
|
||||
legacyFinalChar = L'~';
|
||||
legacyParam = 5 + (virtualKeyCode - VK_PRIOR);
|
||||
}
|
||||
break;
|
||||
case VK_F1:
|
||||
case VK_F2:
|
||||
case VK_F4:
|
||||
legacyFinalChar = L'P' + (virtualKeyCode - VK_F1);
|
||||
break;
|
||||
case VK_F3:
|
||||
// Note: F3 cannot use CSI R as that conflicts with Cursor Position Report.
|
||||
// The kitty spec explicitly removed CSI R for F3.
|
||||
legacyFinalChar = L'~';
|
||||
legacyParam = 13;
|
||||
break;
|
||||
case VK_F5:
|
||||
legacyFinalChar = L'~';
|
||||
legacyParam = 15;
|
||||
break;
|
||||
case VK_F6:
|
||||
case VK_F7:
|
||||
case VK_F8:
|
||||
case VK_F9:
|
||||
case VK_F10:
|
||||
legacyFinalChar = L'~';
|
||||
legacyParam = 17 + (virtualKeyCode - VK_F6);
|
||||
break;
|
||||
case VK_F11:
|
||||
case VK_F12:
|
||||
legacyFinalChar = L'~';
|
||||
legacyParam = 23 + (virtualKeyCode - VK_F11);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// Calculate kitty modifiers early - needed for legacy sequences too
|
||||
// kitty: shift=1, alt=2, ctrl=4, super=8, hyper=16, meta=32, caps_lock=64, num_lock=128
|
||||
uint32_t modifiers = 0;
|
||||
// > Terminals may choose what they want to do about functional keys that have no legacy encoding.
|
||||
//
|
||||
// Specification doesn't specify, so I'm doing it whenever any mode is set.
|
||||
if (kittyKeyCode == 0)
|
||||
{
|
||||
kittyKeyCode = _getKittyKeyCode(key, controlKeyState);
|
||||
}
|
||||
if (kittyKeyCode == 0)
|
||||
{
|
||||
return MakeUnhandled();
|
||||
}
|
||||
|
||||
if (WI_IsFlagSet(_kittyFlags, KittyKeyboardProtocolFlags::ReportAlternateKeys))
|
||||
{
|
||||
// > This [...] causes the terminal to report alternate key values [...]
|
||||
// > See Key codes for details [...]
|
||||
//
|
||||
// > Key codes:
|
||||
// > The unicode-key-code above is the Unicode codepoint representing the key [...]
|
||||
// > Note that the codepoint used is always the lower-case [...] version of the key.
|
||||
|
||||
// > Note that [...] only key events represented as escape codes due to the
|
||||
// > other enhancements in effect will be affected by this enhancement. [...]
|
||||
if (false)
|
||||
{
|
||||
// TODO
|
||||
}
|
||||
|
||||
// > [...] the terminal can [...] [additionally send] the shifted key and base layout key [...].
|
||||
// > The shifted key is [...] the upper-case version of unicode-codepoint,
|
||||
// > or more technically, the shifted version, in the currently active keyboard layout.
|
||||
//
|
||||
// !!NOTE!! that the 2nd line is wrong. On an US layout, Shift+3 is not an "uppercase 3",
|
||||
// it's "#", which is exactly what kitty sends as the "shifted key" parameter.
|
||||
// (This is in addition to "unicode-codepoint" never being defined throughout the spec...)
|
||||
//
|
||||
// > Note that the shifted key must be present only if shift is also present in the modifiers.
|
||||
if (shiftPressed)
|
||||
{
|
||||
// I'm assuming that codepoint is already the shifted version if shift is pressed.
|
||||
kittyAltKeyCodeShifted = codepoint;
|
||||
}
|
||||
kittyAltKeyCodeBase = _getBaseLayoutCodepoint(virtualKeyCode);
|
||||
}
|
||||
|
||||
// Modifiers: shift=1, alt=2, ctrl=4, super=8, hyper=16, meta=32, caps_lock=64, num_lock=128
|
||||
if (WI_IsFlagSet(controlKeyState, SHIFT_PRESSED))
|
||||
{
|
||||
modifiers |= 1;
|
||||
kittyModifiers |= 1;
|
||||
}
|
||||
if (WI_IsAnyFlagSet(controlKeyState, ALT_PRESSED))
|
||||
{
|
||||
modifiers |= 2;
|
||||
kittyModifiers |= 2;
|
||||
}
|
||||
if (WI_IsAnyFlagSet(controlKeyState, CTRL_PRESSED))
|
||||
{
|
||||
modifiers |= 4;
|
||||
kittyModifiers |= 4;
|
||||
}
|
||||
// Per spec: "Lock modifiers are not reported for text producing keys, to keep them
|
||||
// usable in legacy programs. To get lock modifiers for all keys use the Report all
|
||||
// keys as escape codes enhancement." So we report them for functional keys always,
|
||||
// and for text-producing keys only when ReportAllKeys is set.
|
||||
if (functionalKeyCode != 0 || reportAllKeys)
|
||||
// > Lock modifiers are not reported for text producing keys, [...].
|
||||
// > To get lock modifiers for all keys use the Report all keys as escape codes enhancement.
|
||||
if (!_codepointIsText(kittyKeyCode) || WI_IsFlagSet(_kittyFlags, KittyKeyboardProtocolFlags::ReportAllKeysAsEscapeCodes))
|
||||
{
|
||||
if (WI_IsFlagSet(controlKeyState, CAPSLOCK_ON))
|
||||
{
|
||||
modifiers |= 64;
|
||||
kittyModifiers |= 64;
|
||||
}
|
||||
if (WI_IsFlagSet(controlKeyState, NUMLOCK_ON))
|
||||
{
|
||||
modifiers |= 128;
|
||||
kittyModifiers |= 128;
|
||||
}
|
||||
}
|
||||
const auto encodedModifiers = 1 + modifiers;
|
||||
|
||||
// Determine event type: 1=press, 2=repeat, 3=release
|
||||
uint32_t eventType = 1;
|
||||
if (!isKeyDown)
|
||||
{
|
||||
eventType = 3;
|
||||
}
|
||||
else if (isRepeat)
|
||||
{
|
||||
eventType = 2;
|
||||
}
|
||||
std::wstring seq;
|
||||
seq.append(_csi);
|
||||
|
||||
// If this is a key that uses legacy CSI sequences, generate it
|
||||
if (legacyFinalChar != 0)
|
||||
if (legacyFinal)
|
||||
{
|
||||
// Format: CSI param ; modifiers ~ or CSI param ; modifiers : event-type ~
|
||||
std::wstring seq;
|
||||
seq.append(_csi);
|
||||
// > The only exceptions are the Enter, Tab and Backspace keys
|
||||
// > which still generate the same bytes as in legacy mode [...]
|
||||
switch (legacyFinal)
|
||||
{
|
||||
case '\n':
|
||||
return MakeOutput(_inputMode.test(Mode::LineFeed) ? L"\r\n" : L"\r");
|
||||
case '\t':
|
||||
if (shiftPressed)
|
||||
{
|
||||
return MakeOutput(fmt::format(FMT_COMPILE(L"{}Z"), _csi));
|
||||
}
|
||||
return MakeOutput(L"\t");
|
||||
case 0x08:
|
||||
return MakeOutput(_inputMode.test(Mode::BackarrowKey) ? L"\x08" : L"\x7f");
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// Legacy format: CSI [param] [; modifiers [:event-type]] final
|
||||
if (legacyParam > 1)
|
||||
{
|
||||
fmt::format_to(std::back_inserter(seq), FMT_COMPILE(L"{}"), legacyParam);
|
||||
}
|
||||
if (encodedModifiers > 1 || (reportEventTypes && eventType > 1))
|
||||
if (kittyModifiers != 0 || kittyEventType != 0)
|
||||
{
|
||||
fmt::format_to(std::back_inserter(seq), FMT_COMPILE(L";{}"), encodedModifiers);
|
||||
if (reportEventTypes && eventType > 1)
|
||||
fmt::format_to(std::back_inserter(seq), FMT_COMPILE(L";{}"), 1 + kittyModifiers);
|
||||
if (kittyEventType != 0)
|
||||
{
|
||||
fmt::format_to(std::back_inserter(seq), FMT_COMPILE(L":{}"), eventType);
|
||||
fmt::format_to(std::back_inserter(seq), FMT_COMPILE(L":{}"), kittyEventType);
|
||||
}
|
||||
}
|
||||
seq.push_back(legacyFinalChar);
|
||||
return seq;
|
||||
seq.push_back(legacyFinal);
|
||||
return MakeOutput(seq);
|
||||
}
|
||||
|
||||
// According to kitty protocol:
|
||||
// > the codepoint used is always the lower-case (or more technically, un-shifted) version of the key
|
||||
uint32_t keyCode = functionalKeyCode;
|
||||
if (keyCode == 0)
|
||||
{
|
||||
// For alphabetic keys, use the virtual key code converted to lowercase.
|
||||
// We can't use unicodeChar because when Ctrl is pressed, unicodeChar
|
||||
// becomes the control character (e.g., Ctrl+C gives unicodeChar=0x03).
|
||||
if (virtualKeyCode >= 'A' && virtualKeyCode <= 'Z')
|
||||
{
|
||||
keyCode = virtualKeyCode + 32; // Convert to lowercase ('A'->'a')
|
||||
}
|
||||
// Space needs special handling because Ctrl+Space produces NUL (0).
|
||||
else if (virtualKeyCode == VK_SPACE)
|
||||
{
|
||||
keyCode = L' ';
|
||||
}
|
||||
else
|
||||
{
|
||||
keyCode = fullCodepoint;
|
||||
// > CSI unicode-key-code:alternate-key-codes ; modifiers:event-type ; text-as-codepoints u
|
||||
//
|
||||
// unicode-key-code:
|
||||
// > [..] the [lowercase] Unicode codepoint representing the key, as a decimal number.
|
||||
//
|
||||
// alternate-key-codes:
|
||||
// > [...] the terminal can send [...] the shifted key and base layout key, separated by colons.
|
||||
// shifted key:
|
||||
// > [...] the shifted key and base layout key, separated by colons.
|
||||
// > The shifted key is [...] [the result of Shift + unicode-key-code],
|
||||
// > in the currently active keyboard layout.
|
||||
// base layout key:
|
||||
// > [...] the base layout key is the key corresponding to
|
||||
// > the physical key in the standard PC-101 key layout.
|
||||
//
|
||||
// modifiers:
|
||||
// > Modifiers are encoded as a bit field with:
|
||||
// > [...]
|
||||
// For bit encoding see above.
|
||||
// > [...] the modifier value is encoded as [...] 1 + actual modifiers.
|
||||
//
|
||||
// event-type:
|
||||
// > There are three key event types: press, repeat and release.
|
||||
// To summarize unnecessary prose: press=1, repeat=2, release=3.
|
||||
// > If [event-types are requested and] no modifiers are present,
|
||||
// > the modifiers field must have the value 1 [...].
|
||||
//
|
||||
// text-as-codepoints:
|
||||
// > [...] the text associated with key events as a sequence of Unicode code points.
|
||||
// > If multiple code points are present, they must be separated by colons.
|
||||
// > If no known key is associated with the text the key number 0 must be used.
|
||||
// > The associated text must not contain control codes [...].
|
||||
|
||||
// For control characters (e.g., Ctrl+[ produces ESC), use ToUnicodeEx
|
||||
// to get the base character without modifiers.
|
||||
if (!_codepointIsNonControl(keyCode))
|
||||
{
|
||||
const auto hkl = GetKeyboardLayout(GetWindowThreadProcessId(GetForegroundWindow(), nullptr));
|
||||
auto keyState = _getKeyboardState(virtualKeyCode, 0);
|
||||
fmt::format_to(std::back_inserter(seq), FMT_COMPILE(L"{}"), kittyKeyCode);
|
||||
|
||||
// Disable Ctrl and Alt modifiers to obtain the base character mapping.
|
||||
keyState.at(VK_CONTROL) = keyState.at(VK_LCONTROL) = keyState.at(VK_RCONTROL) = 0;
|
||||
keyState.at(VK_MENU) = keyState.at(VK_LMENU) = keyState.at(VK_RMENU) = 0;
|
||||
|
||||
wchar_t buffer[4];
|
||||
const auto result = ToUnicodeEx(virtualKeyCode, 0, keyState.data(), buffer, 4, 4, hkl);
|
||||
|
||||
if (result > 0 && result < 4)
|
||||
{
|
||||
keyCode = _bufferToCodepoint(&buffer[0]);
|
||||
}
|
||||
}
|
||||
|
||||
keyCode = _codepointToLower(keyCode);
|
||||
|
||||
if (!_codepointIsNonControl(keyCode))
|
||||
{
|
||||
return _makeNoOutput();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add alternate keys if requested (shifted key and base layout key)
|
||||
uint32_t shiftedKey = 0;
|
||||
uint32_t baseLayoutKey = 0;
|
||||
if (reportAlternateKeys && functionalKeyCode == 0)
|
||||
{
|
||||
// Shifted key: the uppercase/shifted version of the key
|
||||
if ((modifiers & 1) != 0 && fullCodepoint != 0 && fullCodepoint != keyCode)
|
||||
{
|
||||
shiftedKey = fullCodepoint;
|
||||
}
|
||||
|
||||
// Base layout key: the key in the standard US PC-101 layout.
|
||||
static const auto usLayout = LoadKeyboardLayoutW(L"00000409", 0);
|
||||
if (usLayout != nullptr && virtualKeyCode != 0)
|
||||
{
|
||||
auto keyState = _getKeyboardState(virtualKeyCode, 0); // No modifiers for base key
|
||||
wchar_t baseChar[4]{};
|
||||
const auto result = ToUnicodeEx(virtualKeyCode, 0, keyState.data(), baseChar, 4, 4, usLayout);
|
||||
if (result == 1 && baseChar[0] >= 0x20)
|
||||
{
|
||||
// Use lowercase version of the base layout key
|
||||
auto baseKey = static_cast<uint32_t>(baseChar[0]);
|
||||
if (baseKey >= L'A' && baseKey <= L'Z')
|
||||
{
|
||||
baseKey += 32;
|
||||
}
|
||||
// Only include if different from keyCode
|
||||
if (baseKey != keyCode)
|
||||
{
|
||||
baseLayoutKey = baseKey;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// CSI unicode-key-code:shifted-key:base-layout-key ; modifiers:event-type ; text-as-codepoints u
|
||||
|
||||
std::wstring seq;
|
||||
fmt::format_to(std::back_inserter(seq), FMT_COMPILE(L"{}{}"), _csi, keyCode);
|
||||
|
||||
// Append alternate keys to sequence if present
|
||||
if (shiftedKey != 0 || baseLayoutKey != 0)
|
||||
if (kittyAltKeyCodeShifted != 0 || kittyAltKeyCodeBase != 0)
|
||||
{
|
||||
seq.push_back(L':');
|
||||
if (shiftedKey != 0)
|
||||
if (kittyAltKeyCodeShifted != 0)
|
||||
{
|
||||
fmt::format_to(std::back_inserter(seq), FMT_COMPILE(L"{}"), shiftedKey);
|
||||
fmt::format_to(std::back_inserter(seq), FMT_COMPILE(L"{}"), kittyAltKeyCodeShifted);
|
||||
}
|
||||
if (baseLayoutKey != 0)
|
||||
if (kittyAltKeyCodeBase != 0)
|
||||
{
|
||||
fmt::format_to(std::back_inserter(seq), FMT_COMPILE(L":{}"), baseLayoutKey);
|
||||
fmt::format_to(std::back_inserter(seq), FMT_COMPILE(L":{}"), kittyAltKeyCodeBase);
|
||||
}
|
||||
}
|
||||
|
||||
// Determine if we need to output text-as-codepoints (third field)
|
||||
// Exclude C0 (< 0x20) and C1 (0x80-0x9F) control codes per spec.
|
||||
const auto isValidText = fullCodepoint >= 0x20 && (fullCodepoint < 0x80 || fullCodepoint > 0x9F);
|
||||
const auto needsText = reportText && reportAllKeys && functionalKeyCode == 0 && isValidText && isKeyDown;
|
||||
|
||||
// We need to include modifiers field if:
|
||||
// - modifiers are non-default (encodedModifiers > 1), OR
|
||||
// - we need to report non-press event type, OR
|
||||
// - we need to output text (text is the 3rd field, so we must have 2nd field too)
|
||||
const auto needsEventType = reportEventTypes && eventType > 1;
|
||||
if (encodedModifiers > 1 || needsEventType || needsText)
|
||||
if (kittyModifiers != 0 || kittyEventType != 0 || kittyTextAsCodepoint != 0)
|
||||
{
|
||||
// Per spec: "If no modifiers are present, the modifiers field must have the value 1"
|
||||
// when event type sub-field is needed.
|
||||
fmt::format_to(std::back_inserter(seq), FMT_COMPILE(L";{}"), encodedModifiers);
|
||||
if (needsEventType)
|
||||
fmt::format_to(std::back_inserter(seq), FMT_COMPILE(L";{}"), 1 + kittyModifiers);
|
||||
if (kittyEventType != 0)
|
||||
{
|
||||
fmt::format_to(std::back_inserter(seq), FMT_COMPILE(L":{}"), eventType);
|
||||
fmt::format_to(std::back_inserter(seq), FMT_COMPILE(L":{}"), kittyEventType);
|
||||
}
|
||||
if (needsText)
|
||||
if (kittyTextAsCodepoint != 0)
|
||||
{
|
||||
fmt::format_to(std::back_inserter(seq), FMT_COMPILE(L";{}"), fullCodepoint);
|
||||
fmt::format_to(std::back_inserter(seq), FMT_COMPILE(L";{}"), kittyTextAsCodepoint);
|
||||
}
|
||||
}
|
||||
|
||||
seq.push_back(L'u');
|
||||
return seq;
|
||||
return MakeOutput(std::move(seq));
|
||||
}
|
||||
|
||||
// See: https://sw.kovidgoyal.net/kitty/keyboard-protocol/#functional-key-definitions
|
||||
// NOTE: The definition documents keys named as KP_*, which are keypad keys.
|
||||
uint32_t TerminalInput::_getKittyFunctionalKeyCode(const WORD virtualKeyCode, const WORD virtualScanCode, const DWORD controlKeyState) noexcept
|
||||
int32_t TerminalInput::_getKittyKeyCode(const KEY_EVENT_RECORD& key, DWORD controlKeyState) noexcept
|
||||
{
|
||||
const auto virtualKeyCode = key.wVirtualKeyCode;
|
||||
|
||||
if ((virtualKeyCode >= 'A' && virtualKeyCode <= 'Z') ||
|
||||
(virtualKeyCode >= '0' && virtualKeyCode <= '9'))
|
||||
{
|
||||
return virtualKeyCode;
|
||||
}
|
||||
|
||||
const auto isEnhanced = WI_IsFlagSet(controlKeyState, ENHANCED_KEY);
|
||||
|
||||
switch (virtualKeyCode)
|
||||
@@ -1301,7 +1267,7 @@ uint32_t TerminalInput::_getKittyFunctionalKeyCode(const WORD virtualKeyCode, co
|
||||
|
||||
// Modifier keys
|
||||
case VK_SHIFT:
|
||||
return virtualScanCode == 0x2A ? 57441 : 57447; // LEFT_SHIFT : RIGHT_SHIFT
|
||||
return key.wVirtualScanCode == 0x2A ? 57441 : 57447; // LEFT_SHIFT : RIGHT_SHIFT
|
||||
case VK_LSHIFT:
|
||||
return 57441; // LEFT_SHIFT
|
||||
case VK_RSHIFT:
|
||||
@@ -1333,9 +1299,9 @@ std::vector<uint8_t>& TerminalInput::_getKittyStack() noexcept
|
||||
return _inAlternateBuffer ? _kittyAltStack : _kittyMainStack;
|
||||
}
|
||||
|
||||
bool TerminalInput::_codepointIsNonControl(uint32_t cp) noexcept
|
||||
bool TerminalInput::_codepointIsText(uint32_t cp) noexcept
|
||||
{
|
||||
return cp > 0x1f && (cp < 0x7f || cp > 0x9f);
|
||||
return cp > 0x1f && (cp < 0x7f || cp > 0x9f) && (cp < 57344 || cp > 63743);
|
||||
}
|
||||
|
||||
TerminalInput::CodepointBuffer TerminalInput::_codepointToBuffer(uint32_t cp) noexcept
|
||||
@@ -1369,14 +1335,49 @@ uint32_t TerminalInput::_bufferToCodepoint(const wchar_t* str) noexcept
|
||||
|
||||
uint32_t TerminalInput::_codepointToLower(uint32_t cp) noexcept
|
||||
{
|
||||
auto cb = _codepointToBuffer(cp);
|
||||
// NOTE: MSDN states that `lpSrcStr == lpDestStr` is valid for LCMAP_LOWERCASE.
|
||||
const auto len = LCMapStringW(LOCALE_INVARIANT, LCMAP_LOWERCASE, cb.buf, cb.len, cb.buf, gsl::narrow_cast<int>(std::size(cb.buf)));
|
||||
// NOTE: LCMapStringW returns the length including the null terminator. I'm not checking for it,
|
||||
// because after decades, LCMapStringW should be reliable enough to return len==0 for OOM.
|
||||
if (len > 1)
|
||||
if (cp < 0x80)
|
||||
{
|
||||
return _bufferToCodepoint(cb.buf);
|
||||
return til::tolower_ascii(cp);
|
||||
}
|
||||
return cp;
|
||||
|
||||
auto cb = _codepointToBuffer(cp);
|
||||
return _bufferToLowerCodepoint(cb.buf, gsl::narrow_cast<int>(std::size(cb.buf)));
|
||||
}
|
||||
|
||||
uint32_t TerminalInput::_bufferToLowerCodepoint(wchar_t* buf, int cap) noexcept
|
||||
{
|
||||
// NOTE: MSDN states that `lpSrcStr == lpDestStr` is valid for LCMAP_LOWERCASE.
|
||||
const auto len = LCMapStringW(LOCALE_INVARIANT, LCMAP_LOWERCASE, buf, -1, buf, cap);
|
||||
// NOTE: LCMapStringW returns the length including the null terminator.
|
||||
if (len == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
return _bufferToCodepoint(buf);
|
||||
}
|
||||
|
||||
uint32_t TerminalInput::_getBaseLayoutCodepoint(const WORD vkey) noexcept
|
||||
{
|
||||
// > The base layout key is the key corresponding to the physical key in the standard PC-101 key layout.
|
||||
static const auto usLayout = LoadKeyboardLayoutW(L"00000409", 0);
|
||||
|
||||
if (!usLayout || !vkey)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
wchar_t baseChar[4];
|
||||
const auto keyState = _getKeyboardState(vkey, 0);
|
||||
const auto result = ToUnicodeEx(vkey, 0, keyState.data(), baseChar, 4, 4, usLayout);
|
||||
|
||||
if (result == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
// > [...] pressing the ctrl+С key will be ctrl+c in the standard layout.
|
||||
// > So the terminal should send the base layout key as 99 corresponding to the c key.
|
||||
//
|
||||
// Why use many words when few do trick? base-layout = lowercase.
|
||||
return _bufferToLowerCodepoint(baseChar, gsl::narrow_cast<int>(std::size(baseChar)));
|
||||
}
|
||||
|
||||
@@ -52,11 +52,11 @@ namespace Microsoft::Console::VirtualTerminal
|
||||
struct KittyKeyboardProtocolFlags
|
||||
{
|
||||
static constexpr uint8_t None = 0;
|
||||
static constexpr uint8_t Disambiguate = 1 << 0; // Disambiguate escape codes
|
||||
static constexpr uint8_t ReportEventTypes = 1 << 1; // Report event types (press/repeat/release)
|
||||
static constexpr uint8_t ReportAlternateKeys = 1 << 2; // Report alternate keys
|
||||
static constexpr uint8_t ReportAllKeys = 1 << 3; // Report all keys as escape codes
|
||||
static constexpr uint8_t ReportText = 1 << 4; // Report associated text
|
||||
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
|
||||
@@ -111,8 +111,8 @@ namespace Microsoft::Console::VirtualTerminal
|
||||
bool _forceDisableWin32InputMode{ false };
|
||||
bool _inAlternateBuffer{ false };
|
||||
|
||||
// Kitty keyboard protocol state - separate stacks for main and alternate screen buffers
|
||||
static constexpr size_t KittyStackMaxSize = 16;
|
||||
// Kitty keyboard protocol state
|
||||
static constexpr size_t KittyStackMaxSize = 8;
|
||||
bool _forceDisableKittyKeyboardProtocol = false;
|
||||
uint8_t _kittyFlags = 0;
|
||||
std::vector<uint8_t> _kittyMainStack;
|
||||
@@ -123,19 +123,21 @@ namespace Microsoft::Console::VirtualTerminal
|
||||
|
||||
void _initKeyboardMap() noexcept;
|
||||
DWORD _trackControlKeyState(const KEY_EVENT_RECORD& key) noexcept;
|
||||
std::array<byte, 256> _getKeyboardState(WORD virtualKeyCode, DWORD controlKeyState) const;
|
||||
static std::array<byte, 256> _getKeyboardState(WORD virtualKeyCode, DWORD controlKeyState);
|
||||
[[nodiscard]] static wchar_t _makeCtrlChar(wchar_t ch);
|
||||
[[nodiscard]] StringType _makeCharOutput(wchar_t ch);
|
||||
[[nodiscard]] static StringType _makeNoOutput() noexcept;
|
||||
void _escapeOutput(StringType& charSequence, bool altIsPressed) const;
|
||||
[[nodiscard]] OutputType _makeWin32Output(const KEY_EVENT_RECORD& key) const;
|
||||
[[nodiscard]] OutputType _makeKittyOutput(const KEY_EVENT_RECORD& key, DWORD controlKeyState);
|
||||
[[nodiscard]] static uint32_t _getKittyFunctionalKeyCode(WORD virtualKeyCode, WORD virtualScanCode, DWORD controlKeyState) noexcept;
|
||||
static int32_t _getKittyKeyCode(const KEY_EVENT_RECORD& key, DWORD controlKeyState) noexcept;
|
||||
std::vector<uint8_t>& _getKittyStack() noexcept;
|
||||
static bool _codepointIsNonControl(uint32_t cp) noexcept;
|
||||
static bool _codepointIsText(uint32_t cp) noexcept;
|
||||
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 vkey) noexcept;
|
||||
|
||||
#pragma region MouseInputState Management
|
||||
// These methods are defined in mouseInputState.cpp
|
||||
|
||||
1537
src/terminal/input/ut_kittyKeyboardProtocol.cpp
Normal file
1537
src/terminal/input/ut_kittyKeyboardProtocol.cpp
Normal file
File diff suppressed because it is too large
Load Diff
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