Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions ci/licenses_golden/licenses_flutter
Original file line number Diff line number Diff line change
Expand Up @@ -1497,6 +1497,8 @@ FILE: ../../../flutter/shell/platform/windows/task_runner_win32.cc
FILE: ../../../flutter/shell/platform/windows/task_runner_win32.h
FILE: ../../../flutter/shell/platform/windows/task_runner_winuwp.cc
FILE: ../../../flutter/shell/platform/windows/task_runner_winuwp.h
FILE: ../../../flutter/shell/platform/windows/text_input_manager.cc
FILE: ../../../flutter/shell/platform/windows/text_input_manager.h
FILE: ../../../flutter/shell/platform/windows/text_input_plugin.cc
FILE: ../../../flutter/shell/platform/windows/text_input_plugin.h
FILE: ../../../flutter/shell/platform/windows/text_input_plugin_delegate.h
Expand Down
12 changes: 7 additions & 5 deletions shell/platform/common/cpp/text_input_model.cc
Original file line number Diff line number Diff line change
Expand Up @@ -67,11 +67,7 @@ void TextInputModel::BeginComposing() {
composing_range_ = TextRange(selection_.start());
}

void TextInputModel::UpdateComposingText(const std::string& composing_text) {
std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t>
utf16_converter;
std::u16string text = utf16_converter.from_bytes(composing_text);

void TextInputModel::UpdateComposingText(const std::u16string& text) {
// Preserve selection if we get a no-op update to the composing region.
if (text.length() == 0 && composing_range_.collapsed()) {
return;
Expand All @@ -82,6 +78,12 @@ void TextInputModel::UpdateComposingText(const std::string& composing_text) {
selection_ = TextRange(composing_range_.end());
}

void TextInputModel::UpdateComposingText(const std::string& text) {
std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t>
utf16_converter;
UpdateComposingText(utf16_converter.from_bytes(text));
}

void TextInputModel::CommitComposing() {
// Preserve selection if no composing text was entered.
if (composing_range_.collapsed()) {
Expand Down
16 changes: 12 additions & 4 deletions shell/platform/common/cpp/text_input_model.h
Original file line number Diff line number Diff line change
Expand Up @@ -46,13 +46,21 @@ class TextInputModel {
// are restricted to the composing range.
void BeginComposing();

// Replaces the composing range with new text.
// Replaces the composing range with new UTF-16 text.
//
// If a selection of non-zero length exists, it is deleted if the composing
// text is non-empty. The composing range is adjusted to the length of
// |composing_text| and the selection base and offset are set to the end of
// the composing range.
void UpdateComposingText(const std::string& composing_text);
// |text| and the selection base and offset are set to the end of the
// composing range.
void UpdateComposingText(const std::u16string& text);

// Replaces the composing range with new UTF-8 text.
//
// If a selection of non-zero length exists, it is deleted if the composing
// text is non-empty. The composing range is adjusted to the length of
// |text| and the selection base and offset are set to the end of the
// composing range.
void UpdateComposingText(const std::string& text);

// Commits composing range to the string.
//
Expand Down
7 changes: 6 additions & 1 deletion shell/platform/windows/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@ source_set("flutter_windows_source") {
"string_conversion.h",
"system_utils.h",
"task_runner.h",
"text_input_manager.cc",
"text_input_manager.h",
"text_input_plugin.cc",
"text_input_plugin.h",
"window_binding_handler.h",
Expand Down Expand Up @@ -103,7 +105,10 @@ source_set("flutter_windows_source") {
"win32_window_proc_delegate_manager.h",
]

libs = [ "dwmapi.lib" ]
libs = [
"dwmapi.lib",
"imm32.lib",
]
}

configs += [
Expand Down
32 changes: 32 additions & 0 deletions shell/platform/windows/flutter_windows_view.cc
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,19 @@ bool FlutterWindowsView::OnKey(int key,
return SendKey(key, scancode, action, character, extended);
}

void FlutterWindowsView::OnComposeBegin() {
SendComposeBegin();
}

void FlutterWindowsView::OnComposeEnd() {
SendComposeEnd();
}

void FlutterWindowsView::OnComposeChange(const std::u16string& text,
int cursor_pos) {
SendComposeChange(text, cursor_pos);
}

void FlutterWindowsView::OnScroll(double x,
double y,
double delta_x,
Expand Down Expand Up @@ -240,6 +253,25 @@ bool FlutterWindowsView::SendKey(int key,
return false;
}

void FlutterWindowsView::SendComposeBegin() {
for (const auto& handler : keyboard_hook_handlers_) {
handler->ComposeBeginHook();
}
}

void FlutterWindowsView::SendComposeEnd() {
for (const auto& handler : keyboard_hook_handlers_) {
handler->ComposeEndHook();
}
}

void FlutterWindowsView::SendComposeChange(const std::u16string& text,
int cursor_pos) {
for (const auto& handler : keyboard_hook_handlers_) {
handler->ComposeChangeHook(text, cursor_pos);
}
}

void FlutterWindowsView::SendScroll(double x,
double y,
double delta_x,
Expand Down
27 changes: 27 additions & 0 deletions shell/platform/windows/flutter_windows_view.h
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,15 @@ class FlutterWindowsView : public WindowBindingHandlerDelegate,
char32_t character,
bool extended) override;

// |WindowBindingHandlerDelegate|
void OnComposeBegin() override;

// |WindowBindingHandlerDelegate|
void OnComposeEnd() override;

// |WindowBindingHandlerDelegate|
void OnComposeChange(const std::u16string& text, int cursor_pos) override;

// |WindowBindingHandlerDelegate|
void OnScroll(double x,
double y,
Expand Down Expand Up @@ -185,6 +194,24 @@ class FlutterWindowsView : public WindowBindingHandlerDelegate,
char32_t character,
bool extended);

// Reports an IME compose begin event.
//
// Triggered when the user begins editing composing text using a multi-step
// input method such as in CJK text input.
void SendComposeBegin();

// Reports an IME compose end event.
//
// Triggered when the user commits the composing text while using a multi-step
// input method such as in CJK text input.
void SendComposeEnd();

// Reports an IME composing region change event.
//
// Triggered when the user edits the composing text while using a multi-step
// input method such as in CJK text input.
void SendComposeChange(const std::u16string& text, int cursor_pos);

// Reports scroll wheel events to Flutter engine.
void SendScroll(double x,
double y,
Expand Down
13 changes: 13 additions & 0 deletions shell/platform/windows/key_event_handler.cc
Original file line number Diff line number Diff line change
Expand Up @@ -243,4 +243,17 @@ bool KeyEventHandler::KeyboardHook(FlutterWindowsView* view,
return true;
}

void KeyEventHandler::ComposeBeginHook() {
// Ignore.
}

void KeyEventHandler::ComposeEndHook() {
// Ignore.
}

void KeyEventHandler::ComposeChangeHook(const std::u16string& text,
int cursor_pos) {
// Ignore.
}

} // namespace flutter
9 changes: 9 additions & 0 deletions shell/platform/windows/key_event_handler.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,15 @@ class KeyEventHandler : public KeyboardHookHandler {
void TextHook(FlutterWindowsView* window,
const std::u16string& text) override;

// |KeyboardHookHandler|
void ComposeBeginHook() override;

// |KeyboardHookHandler|
void ComposeEndHook() override;

// |KeyboardHookHandler|
void ComposeChangeHook(const std::u16string& text, int cursor_pos) override;

private:
KEYBDINPUT* FindPendingEvent(uint64_t id);
void RemovePendingEvent(uint64_t id);
Expand Down
19 changes: 19 additions & 0 deletions shell/platform/windows/keyboard_hook_handler.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,25 @@ class KeyboardHookHandler {
// A function for hooking into Unicode text input.
virtual void TextHook(FlutterWindowsView* view,
const std::u16string& text) = 0;

// Handler for IME compose begin events.
//
// Triggered when the user begins editing composing text using a multi-step
// input method such as in CJK text input.
virtual void ComposeBeginHook() = 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.
virtual void ComposeEndHook() = 0;

// Handler for IME compose change events.
//
// Triggered when the user edits the composing text while using a multi-step
// input method such as in CJK text input.
virtual void ComposeChangeHook(const std::u16string& text,
int cursor_pos) = 0;
};

} // namespace flutter
Expand Down
3 changes: 3 additions & 0 deletions shell/platform/windows/testing/mock_win32_window.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ class MockWin32Window : public Win32Window {
MOCK_METHOD1(OnText, void(const std::u16string&));
MOCK_METHOD5(OnKey, bool(int, int, int, char32_t, bool));
MOCK_METHOD2(OnScroll, void(double, double));
MOCK_METHOD0(OnComposeBegin, void());
MOCK_METHOD0(OnComposeEnd, void());
MOCK_METHOD2(OnComposeChange, void(const std::u16string&, int));
};

} // namespace testing
Expand Down
139 changes: 139 additions & 0 deletions shell/platform/windows/text_input_manager.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "flutter/shell/platform/windows/text_input_manager.h"

#include <imm.h>

#include <memory>

namespace flutter {

void TextInputManager::SetWindowHandle(HWND window_handle) {
window_handle_ = window_handle;
}

void TextInputManager::CreateImeWindow() {
if (window_handle_ == nullptr) {
return;
}

// Some IMEs ignore calls to ::ImmSetCandidateWindow() and use the position of
// the current system caret instead via ::GetCaretPos(). In order to behave
// as expected with these IMEs, we create a temporary system caret.
if (!ime_active_) {
::CreateCaret(window_handle_, nullptr, 1, 1);
}
ime_active_ = true;

// Set the position of the IME windows.
UpdateImeWindow();
}

void TextInputManager::DestroyImeWindow() {
if (window_handle_ == nullptr) {
return;
}

// Destroy the system caret created in CreateImeWindow().
if (ime_active_) {
::DestroyCaret();
}
ime_active_ = false;
}

void TextInputManager::UpdateImeWindow() {
if (window_handle_ == nullptr) {
return;
}

HIMC imm_context = ::ImmGetContext(window_handle_);
if (imm_context) {
MoveImeWindow(imm_context);
::ImmReleaseContext(window_handle_, imm_context);
}
}

void TextInputManager::UpdateCaretRect(const Rect& rect) {
caret_rect_ = rect;

if (window_handle_ == nullptr) {
return;
}

// TODO(cbracken): wrap these in an RAII container.
Copy link
Contributor

Choose a reason for hiding this comment

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

Big +1 on this.

HIMC imm_context = ::ImmGetContext(window_handle_);
if (imm_context) {
MoveImeWindow(imm_context);
::ImmReleaseContext(window_handle_, imm_context);
}
}

long TextInputManager::GetComposingCursorPosition() const {
if (window_handle_ == nullptr) {
return false;
}

HIMC imm_context = ::ImmGetContext(window_handle_);
if (imm_context) {
// Read the cursor position within the composing string.
const int pos =
ImmGetCompositionStringW(imm_context, GCS_CURSORPOS, nullptr, 0);
::ImmReleaseContext(window_handle_, imm_context);
return pos;
}
return -1;
}

std::optional<std::u16string> TextInputManager::GetComposingString() const {
return GetString(GCS_COMPSTR);
}

std::optional<std::u16string> TextInputManager::GetResultString() const {
return GetString(GCS_RESULTSTR);
}

std::optional<std::u16string> TextInputManager::GetString(int type) const {
if (window_handle_ == nullptr || !ime_active_) {
return std::nullopt;
}
HIMC imm_context = ::ImmGetContext(window_handle_);
if (imm_context) {
// Read the composing string length.
const long compose_bytes =
::ImmGetCompositionString(imm_context, type, nullptr, 0);
const long compose_length = compose_bytes / sizeof(wchar_t);
if (compose_length <= 0) {
::ImmReleaseContext(window_handle_, imm_context);
return std::nullopt;
}

std::u16string text(compose_length, '\0');
::ImmGetCompositionString(imm_context, type, &text[0], compose_bytes);
::ImmReleaseContext(window_handle_, imm_context);
return text;
}
return std::nullopt;
}

void TextInputManager::MoveImeWindow(HIMC imm_context) {
if (GetFocus() != window_handle_ || !ime_active_) {
return;
}
LONG x = caret_rect_.left();
LONG y = caret_rect_.top();
::SetCaretPos(x, y);

COMPOSITIONFORM cf = {CFS_POINT, {x, y}};
::ImmSetCompositionWindow(imm_context, &cf);

CANDIDATEFORM candidate_form = {0, CFS_CANDIDATEPOS, {x, y}, {0, 0, 0, 0}};
::ImmSetCandidateWindow(imm_context, &candidate_form);
}

} // namespace flutter
Loading