#1072

Windows: emoji panel input (KEYEVENTF_UNICODE) is silently dropped — surrogate key-up halves poison the pairing

Openkt81opened 3mo ago

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:

  1. D83D (down) → buffered
  2. D83D (up) → paired as (high, high) → decode_utf16 error → discarded
  3. DE0A (down) → buffered
  4. DE0A (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.)

Activity

1
kt813mo ago

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 ReadConsoleInputW capture proving the console side delivers the pair cleanly (so the bug is confined to the parser), the exact failure mechanism in handle_surrogate — (high, high)/(low, low) pairs from the down+up interleaving — and a minimal in-place fix (#1073) that gates on key_down so 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.