This change fixes a bug in Korean input whereby WM_IME_COMPOSITION
messages of type GCS_RESULTSTR were assumed to end composing mode.
This change breaks out an additional handler for "commit composing text"
events. In Japanese/Chinese IMEs, these events typically occur on
selection of a candidate from the candidates list and are mostly
synonymous with an "end composing" event.
In Korean text input, there is no candidates list, but rather a
character is built up as keypresses are handled, and committed as soon
as the character is unambiguously complete; in other words, when either
space/return is pressed or a keypress is received that cannot be
interpreted as a modification of the character being composed and
therefore must be the first keystroke of a new character. In these
cases, we want to commit the previous character without ending the
composition.
To illustrate with an example:
1. User focuses on a text field and sets input mode to Hangul.
2. User presses 'ㄱ'. Composing region contains 'ㄱ'.
3. User presses 'ㅏ'. Composing region is updated to '가'.
4. User presses 'ㄴ'. Composing region is updated to '간'.
5. User presses 'ㅏ'. Result string '가' is committed. Composing region is
updated to '나'.
6. User presses 'ㄷ'. Composing string is updated to '낟'.
7. User presses 'ㅏ'. Result string '나' is committed. Composing region is
updated to '다'.
8. User presses space or enter. Result string '다' is committed.
Composing is ended.
On a non-Korean QWERTY keyboard the following key mappings serve to
perform the above input:
* r -> ㄱ
* k -> ㅏ
* s -> ㄴ
* e -> ㄷ
To support the above, we break out a separate "commit composing" method
and commit on WM_IME_COMPOSITION events of type GCS_RESULTSTR and end
composing on WM_IME_ENDCOMPOSITION events. Further, we eliminate the
workaround in the GCS_RESULTSTR handler for continued composition on
Chinese/Japanese IMEs now that we're no longer ending composition on
that event type.
Under shell/platform/windows, we have a mix of two naming schemes:
* foo_bar_win32.h
* win32_foo_bar.h
This renames files and identifiers to consistently use a Win32
suffix-based approach.
The handlers for the TextInput.setMarkedTextRect and
TextInput.setEditableSizeAndTransform in the win32 embedding deal in
Flutter root view co-ordinates. These need to be converted to window
co-ordinates before being passed to the TextInputManager, which deals in
Win32 window co-ordinates.
This fixes a bug wherein the IME candidates window for CJK input was
incorrectly positioned at display scales other than 100% in the OS
settings.
Fixes: https://github.com/flutter/flutter/issues/76902
This updates the Win32 desktop embedder to support input method (abbreviated IM
or IME) composing regions.
In contrast to languages such as English, where keyboard input is
managed keystroke-by-keystroke, languages such as Japanese require a
multi-step input process wherein the user begins a composing sequence,
during which point their keystrokes are captured by a system input
method and converted into a text sequence. During composing, the user is
able to edit the composing range and manage the conversion from keyboard
input to text before eventually committing the text to the underlying
text input field.
To illustrate this, in Japanese, this sequence might look something like
the following:
1. User types 'k'. The character 'k' is added to the composing region.
Typically, the text 'k' will be inserted inline into the underlying
text field but the composing range will be highlighted in some manner,
frequently with a highlight or underline.
2. User types 'a'. The composing range is replaced with the phonetic
kana character 'か' (ka). The composing range continues to be
highlighted.
3. User types 'k'. The character 'k' is appended to the composing
range such that the highlighted text is now 'かk'
4. User types 'u'. The trailing 'k' is replaced with the phonetic kana
character 'く' (ku) such that the composing range now reads 'かく'
The composing range continues to be highlighted.
5. The user presses the space bar to convert the kana characters to
kanji. The composing range is replaced with '書く' (kaku: to write).
6. The user presses the space bar again to show other conversions. The
user's configured input method (for example, ibus) pops up a
completions menu populated with alternatives such as 各 (kaku:
every), 描く (kaku: to draw), 核 (kaku: pit of a fruit, nucleus), 角
(kaku: angle), etc.
7. The user uses the arrow keys to navigate the completions menu and
select the alternative to input. As they do, the inline composing
region in the text field is updated. It continues to be highlighted
or underlined.
8. The user hits enter to commit the composing region. The text is
committed to the underlying text field and the visual highlighting is
removed.
9. If the user presses another key, a new composing sequence begins.
If a selection is present when composing begins, it is preserved until
the first keypress of input is received, at which point the selection is
deleted. If a composing sequence is aborted before the first keypress,
the selection is preserved. Creating a new selection (with the mouse,
for example) aborts composing and the composing region is automatically
committed. A composing range and selection, both with an extent, are
not permitted to co-exist.
During composing, keyboard navigation via the arrow keys, or home and
end (or equivalent shortcuts) is restricted to the composing range, as
are deletions via backspace and the delete key. This patch adds two new
private convenience methods, `editing_range` and `text_range`. The
former returns the range for which editing is currently active -- the
composing range, if composing, otherwise the full range of the text. The
latter, returns a range from position 0 (inclusive) to `text_.length()`
exclusive.
Windows IME support revolves around two main UI windows: the composition window
and the candidate window. The composition window is a system window overlaid
within the current window bounds which renders the composing string. Flutter
already renders this string itself, so we request that this window be hidden.
The candidate window is a system-rendered dropdown that displays all possible
conversions for the text in the composing region. Since the contents of this
window are specific to the particular IME in use, and because the user may have
installed one or more third-party IMEs, Flutter does not attempt to render this
as a widget itself, but rather delegates to the system-rendered window.
The lifecycle of IME composing begins follows the following event order:
1. WM_IME_SETCONTEXT: on window creation this event is received. We strip the
ISC_SHOWUICOMPOSITIONWINDOW bit from the event lparam before passing it to
DefWindowProc() in order to hide the composition window, which Flutter
already renders itself.
2. WM_IME_STARTCOMPOSITION: triggered whenever the user begins inputting new
text. We use this event to set Flutter's TextInputModel into composing mode.
3. WM_IME_COMPOSITION: triggered on each keypress as the user adds, replaces,
or deletes text in the composing region, navigates with their cursor within
the composing region, or selects a new conversion candidate from the
candidates list.
4. WM_IME_ENDCOMPOSITION: triggered when the user has finished editing the text
in the composing region and decides to commit or abort the composition.
Additionally, the following IME-related events are emitted but not yet handled:
* WM_INPUTLANGCHANGE: triggered whenever the user selects a new language using
the system language selection menu. Since there some language-specific
behaviours to IMEs, we may want to make use of this in the future.
* WM_IME_NOTIFY: triggered to notify of various status events such as opening
or closing the candidate window, setting the conversion mode, etc. None of
these are relevant to Flutter at the moment.
* WM_IME_REQUEST: triggered to notify of various commands/requests such as
triggering reconversion of text, which should begin composition mode, insert
the selected text into the composing region, and allow the user to select new
alternative candidates for the text in question before re-committing their
new selection. This patch doesn't support this feature, but it's an important
feature that we should support in future.
This changes the Windows text handling so that keyboard events are sent to the framework first for handling, and then passed to the text input plugin, so that the framework has a chance to handle keys before they get given to the text field.
This is complicated by the async nature of the interaction with the framework, since Windows wants a synchronous response. So, in this change, I always tell Windows that the event was handled, and if the framework (eventually) responds that it wasn't, then I synthesize a new event and send it with SendEvent.
I also added support for detecting "extended" keys, since that was missing, and converted the OnKey handlers in the API to return a bool to indicate whether or not they have handled the event.
Switches the Windows embedding from the standard C API to the new proctable version, to allow for unit testing of the embedding layer separately from the embedder APIs implementation. This includes moving some engine messaging that was still in flutter_windows to the C++ engine class to better encapsulate the proc table.
Originally font change notification was handled by forwarding
WM_FONTCHANGE to the Flutter HWND, to avoid adding new API surface, but
that's not a good solution in a multi-window scenario, and it would
require a completely different solution for UWP. It also requires
non-obvious plumbing in the runner.
This replaces that with an explicit API, so that there's a clean and
obvious way for the runner to trigger this event.
Add copyright headers in a few files where they were missing.
Trim trailing blank comment line where present, for consistency with
other engine code.
Use the standard libtxt copyright header in one file where it differed
(extra (C) and comma compared to other files in libtxt).
This also amends tools/const_finder/test/const_finder_test.dart to look
for a const an additional four lines down to account for the copyright
header added to the test fixture.
The C++ text input model used by Windows and Linux currently uses UTF-32. The intention was to facilitate handling of arrow keys, backspace/delete, etc., however since part of what is synchronized with the engine is cursor+selection offsets, and those offsets are defined in terms of UTF-16 code units, this causes very bad interactions with the framework-side model.
This converts to using UTF-16, rather than UTF-32, so that the offsets align with the framework. It also adds surrogate pair handling to the operations that adjust indexes, to avoid breaking surrogate pairs. (Arbitrary grapheme cluster handling is out of scope for this PR; while definitely desirable in the long term, surrogate pair handling is much more critical since improper handling yields invalid UTF-16, which breaks the text field).
This partially fixes https://github.com/flutter/flutter/issues/55014. A framework-side fix is also necessary (since currently both the engine and the framework attempt to handle arrow keys, which is another out-of-scope-for-this-PR issue), but even without the framework fix this dramatically improves the cursor behavior on Windows when there are surrogate pairs somewhere in the string since at least the two sides agree on what indexes mean.
Includes minor plumbing changes to the text input plumbing on Windows so that we're not pointlessly converting from UTF-16 to UTF-32 and then back to UTF-16.