Skip to content

[Windows] Fix Korean 3-beolsik IME dropping space, digits and punctuation - #194270

Open
sujeong000 wants to merge 2 commits into
flutter:masterfrom
sujeong000:windows-ime-result-after-endcomposition
Open

sujeong000 wants to merge 2 commits into
flutter:masterfrom
sujeong000:windows-ime-result-after-endcomposition

Conversation

@sujeong000

Copy link
Copy Markdown

Fixes #140537

Problem

With the Microsoft Korean IME set to a 3-beolsik layout (390 or Final), a space, digit or punctuation character typed into a TextField on Windows is silently dropped, both while Hangul is being composed and when nothing is being composed. 2-beolsik is unaffected.

Screen Recordings

Microsoft Korean IME, 3-beolsik 390, windows 10 22H2

Before - the space, digit and period are dropped.

before.mov

After - all characters are inserted.

after.mov

Cause

IMM32 does not guarantee the relative order of WM_IME_COMPOSITION with GCS_RESULTSTR and WM_IME_ENDCOMPOSITION: the WM_IME_COMPOSITION, WM_IME_ENDCOMPOSITION and WM_IME_STARTCOMPOSITION pages describe each message in isolation, and Microsoft's own sample inserts the result string when GCS_RESULTSTR is set without referring to WM_IME_ENDCOMPOSITION at all. Most IMEs send the result first, and FlutterWindow is written for that order: OnImeEndComposition commits the composing text and destroys the IME window, and DefWindowProc is suppressed for these messages so that the result is not inserted twice via WM_IME_CHAR/WM_CHAR.

In the 3-beolsik layouts the IME consumes every key (WM_KEYDOWN with VK_PROCESSKEY) and emits the characters itself. Captured with a Win32 message probe on Windows 10 22H2 (19045), Microsoft Korean IME, 3-beolsik 390:

// "겨" is being composed, user presses Space
WM_KEYDOWN            vk=VK_PROCESSKEY scan=0x39
WM_IME_ENDCOMPOSITION                     <- arrives first; the GCS_RESULTSTR is already queued
WM_IME_COMPOSITION    GCS_RESULTSTR="겨 "  <- arrives after the end

// nothing is being composed, user presses Space
WM_KEYDOWN            vk=VK_PROCESSKEY scan=0x39
WM_IME_ENDCOMPOSITION                     <- no WM_IME_STARTCOMPOSITION was ever sent
WM_IME_COMPOSITION    GCS_RESULTSTR=" "

After WM_IME_ENDCOMPOSITION the composing text had already been committed and TextInputManager::GetString refused to read the result (ime_active_ was false), so the result string was dropped. Native controls are unaffected because DefWindowProc converts the result to WM_IME_CHAR/WM_CHAR regardless of order; Flutter suppresses that path.

Fix

  • FlutterWindow::OnImeEndComposition peeks the message queue (PM_NOREMOVE, the same approach KeyboardManager already uses to look for a following WM_CHAR) and, if a WM_IME_COMPOSITION with GCS_RESULTSTR is already queued, defers ending the composition until that result has been processed. IMEs that send the result first see no change: nothing is queued, so the composition ends immediately as before.
  • FlutterWindow::OnImeComposition applies a result to the composition when one is in progress (unchanged); delivers it via OnText when there is no composition but the end was deferred (the bare-space case above); and ignores a result that arrives after a non-deferred end. The last case happens when the window loses focus during composition: the Korean IME sends WM_IME_ENDCOMPOSITION with nothing queued and then re-sends the committed text as a result, which previously was dropped only as a side effect of the ime_active_ check.
  • TextInputManager::GetResultString no longer requires an active IME window. GetComposingString keeps the previous check, so composing-string handling is unchanged.

Tests

  • MockWindow.OnImeEndCompositionWaitsForQueuedResult: the end is deferred until the queued result, and the result is applied to the composition before OnComposeEnd.
  • MockWindow.OnImeCompositionResultWithoutCompositionIsText: end + result with no composition is delivered via OnText.
  • MockWindow.OnImeCompositionResultAfterEndCompositionIsIgnored: the focus-loss re-send is not inserted again.
  • OnImeCompositionResult and OnImeCompositionResultAndCompose now start a composition first, matching the real message order. MockWindow gained a Win32PeekMessage mock.

Pre-launch Checklist

…ITION

IMM32 does not guarantee the order of WM_IME_COMPOSITION (GCS_RESULTSTR)
and WM_IME_ENDCOMPOSITION. Most IMEs send the result first, and
FlutterWindow relies on that: WM_IME_ENDCOMPOSITION commits the
composing text and destroys the IME window, and DefWindowProc is
suppressed for these messages so that the result is not inserted twice.

The Microsoft Korean IME in the 3-beolsik (390 / Final) layouts consumes
every key and, when a space, digit or punctuation key ends a
composition, sends WM_IME_ENDCOMPOSITION *before* the GCS_RESULTSTR
(for example "겨 "). With nothing being composed it sends
WM_IME_ENDCOMPOSITION followed by the result (" ") without any
WM_IME_STARTCOMPOSITION. In both cases the result string could not be
read after the composition had ended and was dropped, and because
DefWindowProc was suppressed there was no WM_CHAR fallback either, so
the typed character was lost.

When WM_IME_ENDCOMPOSITION arrives while a GCS_RESULTSTR message is
already queued, defer ending the composition until that result has been
applied. A result that arrives with no composition in progress but with
a deferred end is delivered as text input. A result that arrives after a
non-deferred end (the IME re-sends the committed text when the window
loses focus) is ignored so that it is not inserted twice. The result
string is now readable without an active IME window; the composing
string keeps its previous behaviour.

IMEs that send the result before WM_IME_ENDCOMPOSITION never take the
deferred path and behave as before.

Fixes flutter#140537
@sujeong000
sujeong000 requested a review from a team as a code owner October 13, 2026 04:23
@github-actions github-actions Bot added a: text input Entering text in a text field or keyboard related problems engine flutter/engine related. See also e: labels. platform-windows Building on or for Windows specifically a: desktop Running on desktop team-windows Owned by the Windows platform team labels Oct 13, 2026

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request modifies the Windows platform shell to defer ending IME compositions when a result string is queued, resolving issues with certain IME layouts like Microsoft Korean 3-beolsik. It introduces state variables to track composition status, utilizes Win32PeekMessage to detect queued results, and updates TextInputManager to allow reading result strings after composition ends. Unit tests are added to verify these behaviors. The review feedback suggests replacing std::optional::value() with operator* when retrieving the committed text to avoid exception-handling overhead and adhere to the Google C++ Style Guide.

Comment thread engine/src/flutter/shell/platform/windows/flutter_window.cc
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

a: desktop Running on desktop a: text input Entering text in a text field or keyboard related problems engine flutter/engine related. See also e: labels. platform-windows Building on or for Windows specifically team-windows Owned by the Windows platform team

Projects

None yet

Development

Successfully merging this pull request may close these issues.

when using 3-set korean keyboard, textField ignores ' ' - word spacing (space bar key) on Windows

1 participant