| // 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 <cassert> |
| #include <memory> |
| |
| namespace flutter { |
| |
| // RAII wrapper for the Win32 Input Method Manager context. |
| class ImmContext { |
| public: |
| ImmContext(HWND window_handle) |
| : context_(::ImmGetContext(window_handle)), |
| window_handle_(window_handle) { |
| assert(window_handle); |
| } |
| |
| ~ImmContext() { |
| if (context_ != nullptr) { |
| ::ImmReleaseContext(window_handle_, context_); |
| } |
| } |
| |
| // Prevent copying. |
| ImmContext(const ImmContext& other) = delete; |
| ImmContext& operator=(const ImmContext& other) = delete; |
| |
| // Returns true if a valid IMM context has been obtained. |
| bool IsValid() const { return context_ != nullptr; } |
| |
| // Returns the IMM context. |
| HIMC get() { |
| assert(context_); |
| return context_; |
| } |
| |
| private: |
| HWND window_handle_; |
| HIMC context_; |
| }; |
| |
| 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; |
| } |
| |
| ImmContext imm_context(window_handle_); |
| if (imm_context.IsValid()) { |
| MoveImeWindow(imm_context.get()); |
| } |
| } |
| |
| void TextInputManager::UpdateCaretRect(const Rect& rect) { |
| caret_rect_ = rect; |
| |
| if (window_handle_ == nullptr) { |
| return; |
| } |
| |
| ImmContext imm_context(window_handle_); |
| if (imm_context.IsValid()) { |
| MoveImeWindow(imm_context.get()); |
| } |
| } |
| |
| long TextInputManager::GetComposingCursorPosition() const { |
| if (window_handle_ == nullptr) { |
| return false; |
| } |
| |
| ImmContext imm_context(window_handle_); |
| if (imm_context.IsValid()) { |
| // Read the cursor position within the composing string. |
| return ImmGetCompositionString(imm_context.get(), GCS_CURSORPOS, nullptr, |
| 0); |
| } |
| return -1; |
| } |
| |
| std::optional<std::u16string> TextInputManager::GetComposingString() const { |
| return GetString(GCS_COMPSTR); |
| } |
| |
| std::optional<std::u16string> TextInputManager::GetResultString() const { |
| return GetString(GCS_RESULTSTR); |
| } |
| |
| void TextInputManager::AbortComposing() { |
| if (window_handle_ == nullptr || !ime_active_) { |
| return; |
| } |
| |
| ImmContext imm_context(window_handle_); |
| if (imm_context.IsValid()) { |
| // Cancel composing and close the candidates window. |
| ::ImmNotifyIME(imm_context.get(), NI_COMPOSITIONSTR, CPS_CANCEL, 0); |
| ::ImmNotifyIME(imm_context.get(), NI_CLOSECANDIDATE, 0, 0); |
| |
| // Clear the composing string. |
| wchar_t composition_str[] = L""; |
| wchar_t reading_str[] = L""; |
| ::ImmSetCompositionStringW(imm_context.get(), SCS_SETSTR, composition_str, |
| sizeof(wchar_t), reading_str, sizeof(wchar_t)); |
| } |
| } |
| |
| std::optional<std::u16string> TextInputManager::GetString(int type) const { |
| if (window_handle_ == nullptr || !ime_active_) { |
| return std::nullopt; |
| } |
| ImmContext imm_context(window_handle_); |
| if (imm_context.IsValid()) { |
| // Read the composing string length. |
| const long compose_bytes = |
| ::ImmGetCompositionString(imm_context.get(), type, nullptr, 0); |
| const long compose_length = compose_bytes / sizeof(wchar_t); |
| if (compose_length < 0) { |
| return std::nullopt; |
| } |
| |
| std::u16string text(compose_length, '\0'); |
| ::ImmGetCompositionString(imm_context.get(), type, &text[0], compose_bytes); |
| return text; |
| } |
| return std::nullopt; |
| } |
| |
| void TextInputManager::MoveImeWindow(HIMC imm_context) { |
| if (GetFocus() != window_handle_ || !ime_active_) { |
| return; |
| } |
| LONG left = caret_rect_.left(); |
| LONG top = caret_rect_.top(); |
| LONG right = caret_rect_.right(); |
| LONG bottom = caret_rect_.bottom(); |
| ::SetCaretPos(left, bottom); |
| |
| // Set the position of composition text. |
| COMPOSITIONFORM composition_form = {CFS_POINT, {left, top}}; |
| ::ImmSetCompositionWindow(imm_context, &composition_form); |
| |
| // Set the position of candidate window. |
| CANDIDATEFORM candidate_form = { |
| 0, CFS_EXCLUDE, {left, bottom}, {left, top, right, bottom}}; |
| ::ImmSetCandidateWindow(imm_context, &candidate_form); |
| } |
| |
| } // namespace flutter |