diff --git a/implementations/Crafter.Graphics-Window.cpp b/implementations/Crafter.Graphics-Window.cpp index 02abbc4..cec9951 100644 --- a/implementations/Crafter.Graphics-Window.cpp +++ b/implementations/Crafter.Graphics-Window.cpp @@ -1519,14 +1519,26 @@ extern "C" { g_domWindow->heldKeys.insert(code); g_domWindow->onRawKeyDown.Invoke(code); } - // KeyboardEvent.key is the printable form. Forward as UTF-8 - // text input for non-control keys so onTextInput drives input - // fields the same way it does on Win32 / Wayland. - if (keyLen == 1 && static_cast(keyPtr[0]) >= 0x20 - && static_cast(keyPtr[0]) != 0x7F) { + // KeyboardEvent.key is the printable form for character keys, BUT + // it's also the named identifier for non-character keys — + // "Backspace", "Shift", "ArrowLeft", "Enter", … Those are + // multi-byte ASCII strings, and a naive `keyLen > 1` check + // forwards them straight into input fields as literal text. + // + // The reliable discriminator: multi-byte UTF-8 has the high bit + // set on its first byte (lead byte ≥ 0xC0; continuation bytes + // ≥ 0x80). Named keys are pure ASCII letters → first byte < 0x80. + // So forward when: + // • keyLen == 1 and the byte is a printable ASCII char, OR + // • keyLen > 1 and the first byte is ≥ 0x80 (true UTF-8). + // The onRawKeyDown event above already carries the key code for + // control-key consumers (InputField_OnKey handles Backspace/etc + // through that channel). + const auto first = static_cast(keyPtr[0]); + if (keyLen == 1 && first >= 0x20 && first != 0x7F) { g_domWindow->onTextInput.Invoke(std::string_view(keyPtr, static_cast(keyLen))); - } else if (keyLen > 1) { - // Multi-byte UTF-8 (non-ASCII printable). Forward as-is — + } else if (keyLen > 1 && first >= 0x80) { + // Real multi-byte UTF-8 (non-ASCII printable). Forward as-is — // dom-env.js always sends valid UTF-8. g_domWindow->onTextInput.Invoke(std::string_view(keyPtr, static_cast(keyLen))); }