From 82eba7df342def21179454f39e464bbacfdedbfe Mon Sep 17 00:00:00 2001 From: Chris Bracken Date: Mon, 1 Mar 2021 12:04:26 -0800 Subject: [PATCH] Win32: Support Korean input MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- shell/platform/windows/flutter_window_win32.cc | 4 ++++ shell/platform/windows/flutter_window_win32.h | 3 +++ .../windows/flutter_window_win32_unittests.cc | 2 ++ shell/platform/windows/flutter_windows_view.cc | 10 ++++++++++ shell/platform/windows/flutter_windows_view.h | 10 ++++++++++ shell/platform/windows/keyboard_handler_base.h | 11 +++++++++-- shell/platform/windows/keyboard_key_handler.cc | 4 ++++ shell/platform/windows/keyboard_key_handler.h | 3 +++ shell/platform/windows/testing/mock_window_win32.h | 1 + shell/platform/windows/text_input_plugin.cc | 8 ++++++++ shell/platform/windows/text_input_plugin.h | 3 +++ .../windows/window_binding_handler_delegate.h | 7 +++++++ shell/platform/windows/window_win32.cc | 9 ++------- shell/platform/windows/window_win32.h | 3 +++ 14 files changed, 69 insertions(+), 9 deletions(-) diff --git a/shell/platform/windows/flutter_window_win32.cc b/shell/platform/windows/flutter_window_win32.cc index 738cb1484e3fa..fae6558e59957 100644 --- a/shell/platform/windows/flutter_window_win32.cc +++ b/shell/platform/windows/flutter_window_win32.cc @@ -176,6 +176,10 @@ void FlutterWindowWin32::OnComposeBegin() { binding_handler_delegate_->OnComposeBegin(); } +void FlutterWindowWin32::OnComposeCommit() { + binding_handler_delegate_->OnComposeCommit(); +} + void FlutterWindowWin32::OnComposeEnd() { binding_handler_delegate_->OnComposeEnd(); } diff --git a/shell/platform/windows/flutter_window_win32.h b/shell/platform/windows/flutter_window_win32.h index 161c0ed67b5ad..7f67567b1b15f 100644 --- a/shell/platform/windows/flutter_window_win32.h +++ b/shell/platform/windows/flutter_window_win32.h @@ -65,6 +65,9 @@ class FlutterWindowWin32 : public WindowWin32, public WindowBindingHandler { // |WindowWin32| void OnComposeBegin() override; + // |WindowWin32| + void OnComposeCommit() override; + // |WindowWin32| void OnComposeEnd() override; diff --git a/shell/platform/windows/flutter_window_win32_unittests.cc b/shell/platform/windows/flutter_window_win32_unittests.cc index ff4fca9a3e236..1ace5f7e70be1 100644 --- a/shell/platform/windows/flutter_window_win32_unittests.cc +++ b/shell/platform/windows/flutter_window_win32_unittests.cc @@ -78,6 +78,7 @@ class SpyKeyboardKeyHandler : public KeyboardHandlerBase { MOCK_METHOD2(TextHook, void(FlutterWindowsView* window, const std::u16string& text)); MOCK_METHOD0(ComposeBeginHook, void()); + MOCK_METHOD0(ComposeCommitHook, void()); MOCK_METHOD0(ComposeEndHook, void()); MOCK_METHOD2(ComposeChangeHook, void(const std::u16string& text, int cursor_pos)); @@ -112,6 +113,7 @@ class SpyTextInputPlugin : public KeyboardHandlerBase, MOCK_METHOD2(TextHook, void(FlutterWindowsView* window, const std::u16string& text)); MOCK_METHOD0(ComposeBeginHook, void()); + MOCK_METHOD0(ComposeCommitHook, void()); MOCK_METHOD0(ComposeEndHook, void()); MOCK_METHOD2(ComposeChangeHook, void(const std::u16string& text, int cursor_pos)); diff --git a/shell/platform/windows/flutter_windows_view.cc b/shell/platform/windows/flutter_windows_view.cc index bda100930bb00..1ce3d13d0a7d5 100644 --- a/shell/platform/windows/flutter_windows_view.cc +++ b/shell/platform/windows/flutter_windows_view.cc @@ -210,6 +210,10 @@ void FlutterWindowsView::OnComposeBegin() { SendComposeBegin(); } +void FlutterWindowsView::OnComposeCommit() { + SendComposeCommit(); +} + void FlutterWindowsView::OnComposeEnd() { SendComposeEnd(); } @@ -329,6 +333,12 @@ void FlutterWindowsView::SendComposeBegin() { } } +void FlutterWindowsView::SendComposeCommit() { + for (const auto& handler : keyboard_handlers_) { + handler->ComposeCommitHook(); + } +} + void FlutterWindowsView::SendComposeEnd() { for (const auto& handler : keyboard_handlers_) { handler->ComposeEndHook(); diff --git a/shell/platform/windows/flutter_windows_view.h b/shell/platform/windows/flutter_windows_view.h index 865b0b9e09fc1..79e3576251dfa 100644 --- a/shell/platform/windows/flutter_windows_view.h +++ b/shell/platform/windows/flutter_windows_view.h @@ -111,6 +111,9 @@ class FlutterWindowsView : public WindowBindingHandlerDelegate, // |WindowBindingHandlerDelegate| void OnComposeBegin() override; + // |WindowBindingHandlerDelegate| + void OnComposeCommit() override; + // |WindowBindingHandlerDelegate| void OnComposeEnd() override; @@ -202,6 +205,13 @@ class FlutterWindowsView : public WindowBindingHandlerDelegate, // input method such as in CJK text input. void SendComposeBegin(); + // Reports an IME compose commit event. + // + // Triggered when the user commits the current composing text while using a + // multi-step input method such as in CJK text input. Composing continues with + // the next keypress. + void SendComposeCommit(); + // Reports an IME compose end event. // // Triggered when the user commits the composing text while using a multi-step diff --git a/shell/platform/windows/keyboard_handler_base.h b/shell/platform/windows/keyboard_handler_base.h index 44e91bc29c492..e7545443db3be 100644 --- a/shell/platform/windows/keyboard_handler_base.h +++ b/shell/platform/windows/keyboard_handler_base.h @@ -45,10 +45,17 @@ class KeyboardHandlerBase { // input method such as in CJK text input. virtual void ComposeBeginHook() = 0; + // Handler for IME compose commit events. + // + // Triggered when the user commits the current composing text while using a + // multi-step input method such as in CJK text input. Composing continues with + // the next keypress. + virtual void ComposeCommitHook() = 0; + // Handler for IME compose end events. // - // Triggered when the user commits the composing text while using a multi-step - // input method such as in CJK text input. + // Triggered when the user ends editing composing text while using a + // multi-step input method such as in CJK text input. virtual void ComposeEndHook() = 0; // Handler for IME compose change events. diff --git a/shell/platform/windows/keyboard_key_handler.cc b/shell/platform/windows/keyboard_key_handler.cc index a1137644d7812..0a7e48772bd63 100644 --- a/shell/platform/windows/keyboard_key_handler.cc +++ b/shell/platform/windows/keyboard_key_handler.cc @@ -181,6 +181,10 @@ void KeyboardKeyHandler::ComposeBeginHook() { // Ignore. } +void KeyboardKeyHandler::ComposeCommitHook() { + // Ignore. +} + void KeyboardKeyHandler::ComposeEndHook() { // Ignore. } diff --git a/shell/platform/windows/keyboard_key_handler.h b/shell/platform/windows/keyboard_key_handler.h index 811ee30b04e57..ec3f3efdddf6c 100644 --- a/shell/platform/windows/keyboard_key_handler.h +++ b/shell/platform/windows/keyboard_key_handler.h @@ -107,6 +107,9 @@ class KeyboardKeyHandler : public KeyboardHandlerBase { // |KeyboardHandlerBase| void ComposeBeginHook() override; + // |KeyboardHandlerBase| + void ComposeCommitHook() override; + // |KeyboardHandlerBase| void ComposeEndHook() override; diff --git a/shell/platform/windows/testing/mock_window_win32.h b/shell/platform/windows/testing/mock_window_win32.h index 1854dda78b70c..e1191a01de499 100644 --- a/shell/platform/windows/testing/mock_window_win32.h +++ b/shell/platform/windows/testing/mock_window_win32.h @@ -42,6 +42,7 @@ class MockWin32Window : public WindowWin32 { MOCK_METHOD6(OnKey, bool(int, int, int, char32_t, bool, bool)); MOCK_METHOD2(OnScroll, void(double, double)); MOCK_METHOD0(OnComposeBegin, void()); + MOCK_METHOD0(OnComposeCommit, void()); MOCK_METHOD0(OnComposeEnd, void()); MOCK_METHOD2(OnComposeChange, void(const std::u16string&, int)); MOCK_METHOD4(DefaultWindowProc, LRESULT(HWND, UINT, WPARAM, LPARAM)); diff --git a/shell/platform/windows/text_input_plugin.cc b/shell/platform/windows/text_input_plugin.cc index 49d5f7f0e46dc..f898b30b813c6 100644 --- a/shell/platform/windows/text_input_plugin.cc +++ b/shell/platform/windows/text_input_plugin.cc @@ -111,6 +111,14 @@ void TextInputPlugin::ComposeBeginHook() { SendStateUpdate(*active_model_); } +void TextInputPlugin::ComposeCommitHook() { + if (active_model_ == nullptr) { + return; + } + active_model_->CommitComposing(); + SendStateUpdate(*active_model_); +} + void TextInputPlugin::ComposeEndHook() { if (active_model_ == nullptr) { return; diff --git a/shell/platform/windows/text_input_plugin.h b/shell/platform/windows/text_input_plugin.h index b091d12d1bba5..a615576d5d49c 100644 --- a/shell/platform/windows/text_input_plugin.h +++ b/shell/platform/windows/text_input_plugin.h @@ -47,6 +47,9 @@ class TextInputPlugin : public KeyboardHandlerBase { // |KeyboardHandlerBase| void ComposeBeginHook() override; + // |KeyboardHandlerBase| + void ComposeCommitHook() override; + // |KeyboardHandlerBase| void ComposeEndHook() override; diff --git a/shell/platform/windows/window_binding_handler_delegate.h b/shell/platform/windows/window_binding_handler_delegate.h index df3499545c91f..fe839e6295316 100644 --- a/shell/platform/windows/window_binding_handler_delegate.h +++ b/shell/platform/windows/window_binding_handler_delegate.h @@ -59,6 +59,13 @@ class WindowBindingHandlerDelegate { // input method such as in CJK text input. virtual void OnComposeBegin() = 0; + // Notifies the delegate that IME composing region have been committed. + // + // Triggered when the user commits the current composing text while using a + // multi-step input method such as in CJK text input. Composing continues with + // the next keypress. + virtual void OnComposeCommit() = 0; + // Notifies the delegate that IME composing mode has ended. // // Triggered when the user commits the composing text while using a multi-step diff --git a/shell/platform/windows/window_win32.cc b/shell/platform/windows/window_win32.cc index f7682527a129b..7b1e007afc95c 100644 --- a/shell/platform/windows/window_win32.cc +++ b/shell/platform/windows/window_win32.cc @@ -142,18 +142,13 @@ void WindowWin32::OnImeComposition(UINT const message, OnComposeChange(text.value(), pos); } } else if (lparam & GCS_RESULTSTR) { + // Commit but don't end composing. // Read the committed composing string. long pos = text_input_manager_.GetComposingCursorPosition(); std::optional text = text_input_manager_.GetResultString(); if (text) { OnComposeChange(text.value(), pos); - } - // Next, try reading the composing string. Some Japanese IMEs send a message - // containing both a GCS_RESULTSTR and a GCS_COMPSTR when one composition is - // committed and another immediately started. - text = text_input_manager_.GetResultString(); - if (text) { - OnComposeChange(text.value(), pos); + OnComposeCommit(); } } } diff --git a/shell/platform/windows/window_win32.h b/shell/platform/windows/window_win32.h index ed1d8c742f2de..db4356b2a4a11 100644 --- a/shell/platform/windows/window_win32.h +++ b/shell/platform/windows/window_win32.h @@ -107,6 +107,9 @@ class WindowWin32 { // Called when IME composing begins. virtual void OnComposeBegin() = 0; + // Called when IME composing text is committed. + virtual void OnComposeCommit() = 0; + // Called when IME composing ends. virtual void OnComposeEnd() = 0;