This commit is contained in:
Leonard Hecker
2026-02-02 14:02:11 +01:00
parent 5d0e8c238c
commit a65bff17d8
11 changed files with 2945 additions and 377 deletions

View File

@@ -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)));
}

View File

@@ -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

File diff suppressed because it is too large Load Diff

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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