Summary
Characters inserted via the Windows emoji panel (Win+.) — or any injector using SendInput with KEYEVENTF_UNICODE — never come out of crossterm's event stream on Windows. BMP characters survive (single code unit), but anything needing a surrogate pair (emoji, many symbols) is dropped silently: no event, no error.
Environment
crossterm 0.29.0, Windows 11, Windows Terminal + ConPTY, raw-mode TUI reading via event::poll/event::read.
Evidence the console side is fine
A raw ReadConsoleInputW probe (bypassing crossterm entirely) shows the injection arriving cleanly — each UTF-16 code unit is delivered as a key-down and a key-up KEY_EVENT, with wVirtualKeyCode = 0:
KEY down=1 vk=0x00 unit=U+D83D (HIGH-SURROGATE)
KEY down=0 vk=0x00 unit=U+D83D (HIGH-SURROGATE)
KEY down=1 vk=0x00 unit=U+DE0A (LOW-SURROGATE)
KEY down=0 vk=0x00 unit=U+DE0A (LOW-SURROGATE)(U+1F60A, 😊.)
Mechanism
parse_key_event_record (src/event/sys/windows/parse.rs) forwards any u_char in 0xD800..=0xDFFF as WindowsKeyEvent::Surrogate for key-down and key-up records alike, and handle_surrogate pairs consecutive values blindly. With the down+up interleaving above:
D83D(down) → bufferedD83D(up) → paired as (high, high) →decode_utf16error → discardedDE0A(down) → bufferedDE0A(up) → paired as (low, low) → error → discarded
Every half is consumed; nothing is emitted.
Fix
Let only key-down records carry a surrogate into the pairing, and drop key-up halves without disturbing a buffered value — the down halves then pair correctly. One guarded early-return; PR follows.
Verified end-to-end with the guard in place: emoji-panel input composes correctly and reaches a PTY child through our TUI (dogfooded via [patch.crates-io] in https://github.com/kt81/banto).
(The VK_MENU alt-code path also emits surrogates on key-up by design — that path is deliberately untouched; this is only about the general branch.)
Prior art I should have linked when filing — apologies for not searching first. This is the same underlying bug as #787 (2023), #848 (2023, fix attempted in #857), and #1008 (2025), where @himhimlo independently diagnosed the same key-down/key-up double-delivery and offered a workaround in #1009 — credit to them for reaching the cause first.
What this issue adds to that record: a raw
ReadConsoleInputWcapture proving the console side delivers the pair cleanly (so the bug is confined to the parser), the exact failure mechanism inhandle_surrogate— (high, high)/(low, low) pairs from the down+up interleaving — and a minimal in-place fix (#1073) that gates onkey_downso only one half of each unit enters the pairing.Maintainers: happy to have this closed as a duplicate and the diagnosis folded into whichever report you prefer to keep — the goal is one of the three pending fixes landing, not a fourth thread.