Commit e1c1d3de authored by Dave Tapuska's avatar Dave Tapuska Committed by Commit Bot

Resurrect Text Services Framework support on Windows.

The bulk of this change is to revert commit
0b15dad9. It isn't a direct revert due
to a number of things.
  - The ScopedComPtr interface that was widely used in the old patch no
    longer exists.
  - A number of interfaces change slightly (ImeTextSpans)
  - unsigned/signed compiler warnings

This code was abandoned a few years ago and removed from the repository
because it only worked in the Windows 8 Metro Mode of Chrome. Once that
mode was removed the supporting IME code was removed as well. The code
in its current form is functional but has issues which will be addressed
in follow up patches. It is desirable to move to TSF since there are
additional APIs that work via this API. Like text writing.

Functionality common to both input methods are placed in
InputMethodWinBase. InputMethodWin will be renamed to InputMethodWinImm32
in a followup patch.

This feature is currently disabled but can be enabled via
--enable-features=TSFImeSupport. No functionality changes are expected
with the Imm32 implementation.

Inputing text using Pinyin seems to work; as well as disabling DLL
injection (crbug.com/557798) allows the IME to work correctly with TSF.

There is some broken support with this change in that keydown/up messages,
arrow keys in the omnibox are not delivered. I intend to fix these in
followup changes being that this change is intended to make a previously
reverted change available under a command line flag.

BUG=657623

Change-Id: I89eb1a7ba6b9f95525df6fa83be5d923a03323b2
Reviewed-on: https://chromium-review.googlesource.com/916534
Commit-Queue: Dave Tapuska <dtapuska@chromium.org>
Reviewed-by: default avatarSadrul Chowdhury <sadrul@chromium.org>
Reviewed-by: default avatarBruce Dawson <brucedawson@chromium.org>
Reviewed-by: default avatarShu Chen <shuchen@chromium.org>
Cr-Commit-Position: refs/heads/master@{#541895}
parent 560a916a
......@@ -735,6 +735,8 @@ static_library("test_support") {
"ime/dummy_input_method.h",
"ime/dummy_text_input_client.cc",
"ime/dummy_text_input_client.h",
"ime/win/mock_tsf_bridge.cc",
"ime/win/mock_tsf_bridge.h",
]
deps += [
......@@ -883,6 +885,7 @@ test("ui_base_unittests") {
"ime/input_method_chromeos_unittest.cc",
"ime/win/imm32_manager_unittest.cc",
"ime/win/tsf_input_scope_unittest.cc",
"ime/win/tsf_text_store_unittest.cc",
]
if (is_linux && use_aura && !is_chromeos) {
sources += [ "ime/input_method_auralinux_unittest.cc" ]
......
......@@ -96,8 +96,14 @@ jumbo_component("ime") {
"ui_base_ime_export.h",
"win/imm32_manager.cc",
"win/imm32_manager.h",
"win/tsf_bridge.cc",
"win/tsf_bridge.h",
"win/tsf_event_router.cc",
"win/tsf_event_router.h",
"win/tsf_input_scope.cc",
"win/tsf_input_scope.h",
"win/tsf_text_store.cc",
"win/tsf_text_store.h",
]
# TODO(jschuh): crbug.com/167187 fix size_t to int truncations.
......@@ -179,6 +185,12 @@ jumbo_component("ime") {
cflags = [ "/wd4324" ] # Structure was padded due to __declspec(align()), which is
# uninteresting.
sources += [
"input_method_win_base.cc",
"input_method_win_base.h",
"input_method_win_tsf.cc",
"input_method_win_tsf.h",
]
libs = [ "imm32.lib" ]
}
......
......@@ -22,9 +22,9 @@ ui::IMEEngineHandlerInterface* InputMethodBase::GetEngine() {
return nullptr;
}
InputMethodBase::InputMethodBase()
InputMethodBase::InputMethodBase(internal::InputMethodDelegate* delegate)
: sending_key_event_(false),
delegate_(nullptr),
delegate_(delegate),
text_input_client_(nullptr) {}
InputMethodBase::~InputMethodBase() {
......
......@@ -34,7 +34,7 @@ class UI_BASE_IME_EXPORT InputMethodBase
public base::SupportsWeakPtr<InputMethodBase>,
public IMEInputContextHandlerInterface {
public:
InputMethodBase();
explicit InputMethodBase(internal::InputMethodDelegate* delegate = nullptr);
~InputMethodBase() override;
// Overriden from InputMethod.
......
......@@ -8,12 +8,14 @@
#include "base/memory/ptr_util.h"
#include "build/build_config.h"
#include "ui/base/ime/mock_input_method.h"
#include "ui/base/ui_base_features.h"
#include "ui/gfx/switches.h"
#if defined(OS_CHROMEOS)
#include "ui/base/ime/input_method_chromeos.h"
#elif defined(OS_WIN)
#include "ui/base/ime/input_method_win.h"
#include "ui/base/ime/input_method_win_tsf.h"
#elif defined(OS_MACOSX)
#include "ui/base/ime/input_method_mac.h"
#elif defined(USE_AURA) && defined(USE_X11)
......@@ -55,6 +57,8 @@ std::unique_ptr<InputMethod> CreateInputMethod(
#if defined(OS_CHROMEOS)
return std::make_unique<InputMethodChromeOS>(delegate);
#elif defined(OS_WIN)
if (base::FeatureList::IsEnabled(features::kTSFImeSupport))
return std::make_unique<InputMethodWinTSF>(delegate, widget);
return std::make_unique<InputMethodWin>(delegate, widget);
#elif defined(OS_MACOSX)
return std::make_unique<InputMethodMac>(delegate);
......
......@@ -11,6 +11,9 @@
#elif defined(USE_AURA) && defined(OS_LINUX)
#include "base/logging.h"
#include "ui/base/ime/linux/fake_input_method_context_factory.h"
#elif defined(OS_WIN)
#include "ui/base/ime/input_method_factory.h"
#include "ui/base/ime/win/tsf_bridge.h"
#endif
namespace {
......@@ -27,12 +30,16 @@ namespace ui {
void InitializeInputMethod() {
#if defined(OS_CHROMEOS)
IMEBridge::Initialize();
#elif defined(OS_WIN)
TSFBridge::Initialize();
#endif
}
void ShutdownInputMethod() {
#if defined(OS_CHROMEOS)
IMEBridge::Shutdown();
#elif defined(OS_WIN)
TSFBridge::Shutdown();
#endif
}
......@@ -50,6 +57,10 @@ void InitializeInputMethodForTesting() {
<< "else.";
LinuxInputMethodContextFactory::SetInstance(
g_linux_input_method_context_factory_for_testing);
#elif defined(OS_WIN)
// Make sure COM is initialized because TSF depends on COM.
CoInitialize(nullptr);
TSFBridge::Initialize();
#endif
}
......@@ -64,6 +75,9 @@ void ShutdownInputMethodForTesting() {
LinuxInputMethodContextFactory::SetInstance(NULL);
delete g_linux_input_method_context_factory_for_testing;
g_linux_input_method_context_factory_for_testing = NULL;
#elif defined(OS_WIN)
TSFBridge::Shutdown();
CoUninitialize();
#endif
}
......
......@@ -25,10 +25,6 @@
namespace ui {
namespace {
// Extra number of chars before and after selection (or composition) range which
// is returned to IME for improving conversion accuracy.
static const size_t kExtraNumberOfChars = 20;
ui::EventDispatchDetails DispatcherDestroyedDetails() {
ui::EventDispatchDetails dispatcher_details;
dispatcher_details.dispatcher_destroyed = true;
......@@ -39,14 +35,12 @@ ui::EventDispatchDetails DispatcherDestroyedDetails() {
InputMethodWin::InputMethodWin(internal::InputMethodDelegate* delegate,
HWND toplevel_window_handle)
: toplevel_window_handle_(toplevel_window_handle),
: InputMethodWinBase(delegate, toplevel_window_handle),
pending_requested_direction_(base::i18n::UNKNOWN_DIRECTION),
accept_carriage_return_(false),
enabled_(false),
is_candidate_popup_open_(false),
composing_window_handle_(NULL),
weak_ptr_factory_(this) {
SetDelegate(delegate);
imm32_manager_.SetInputLanguage();
}
......@@ -313,44 +307,7 @@ void InputMethodWin::OnDidChangeFocusedClient(
// bounds has not changed.
OnCaretBoundsChanged(focused);
}
if (focused_before != focused)
accept_carriage_return_ = false;
}
LRESULT InputMethodWin::OnChar(HWND window_handle,
UINT message,
WPARAM wparam,
LPARAM lparam,
const base::NativeEvent& event,
BOOL* handled) {
*handled = TRUE;
// We need to send character events to the focused text input client event if
// its text input type is ui::TEXT_INPUT_TYPE_NONE.
if (GetTextInputClient()) {
const base::char16 kCarriageReturn = L'\r';
const base::char16 ch = static_cast<base::char16>(wparam);
// A mask to determine the previous key state from |lparam|. The value is 1
// if the key is down before the message is sent, or it is 0 if the key is
// up.
const uint32_t kPrevKeyDownBit = 0x40000000;
if (ch == kCarriageReturn && !(lparam & kPrevKeyDownBit))
accept_carriage_return_ = true;
// Conditionally ignore '\r' events to work around crbug.com/319100.
// TODO(yukawa, IME): Figure out long-term solution.
if (ch != kCarriageReturn || accept_carriage_return_) {
ui::KeyEvent char_event(event);
GetTextInputClient()->InsertChar(char_event);
}
}
// Explicitly show the system menu at a good location on [Alt]+[Space].
// Note: Setting |handled| to FALSE for DefWindowProc triggering of the system
// menu causes undesirable titlebar artifacts in the classic theme.
if (message == WM_SYSCHAR && wparam == VK_SPACE)
gfx::ShowSystemMenu(window_handle);
return 0;
InputMethodWinBase::OnDidChangeFocusedClient(focused_before, focused);
}
LRESULT InputMethodWin::OnImeSetContext(HWND window_handle,
......@@ -481,188 +438,6 @@ LRESULT InputMethodWin::OnImeNotify(UINT message,
return 0;
}
LRESULT InputMethodWin::OnImeRequest(UINT message,
WPARAM wparam,
LPARAM lparam,
BOOL* handled) {
*handled = FALSE;
// Should not receive WM_IME_REQUEST message, if IME is disabled.
const ui::TextInputType type = GetTextInputType();
if (type == ui::TEXT_INPUT_TYPE_NONE ||
type == ui::TEXT_INPUT_TYPE_PASSWORD) {
return 0;
}
switch (wparam) {
case IMR_RECONVERTSTRING:
*handled = TRUE;
return OnReconvertString(reinterpret_cast<RECONVERTSTRING*>(lparam));
case IMR_DOCUMENTFEED:
*handled = TRUE;
return OnDocumentFeed(reinterpret_cast<RECONVERTSTRING*>(lparam));
case IMR_QUERYCHARPOSITION:
*handled = TRUE;
return OnQueryCharPosition(reinterpret_cast<IMECHARPOSITION*>(lparam));
default:
return 0;
}
}
LRESULT InputMethodWin::OnDocumentFeed(RECONVERTSTRING* reconv) {
ui::TextInputClient* client = GetTextInputClient();
if (!client)
return 0;
gfx::Range text_range;
if (!client->GetTextRange(&text_range) || text_range.is_empty())
return 0;
bool result = false;
gfx::Range target_range;
if (client->HasCompositionText())
result = client->GetCompositionTextRange(&target_range);
if (!result || target_range.is_empty()) {
if (!client->GetSelectionRange(&target_range) ||
!target_range.IsValid()) {
return 0;
}
}
if (!text_range.Contains(target_range))
return 0;
if (target_range.GetMin() - text_range.start() > kExtraNumberOfChars)
text_range.set_start(target_range.GetMin() - kExtraNumberOfChars);
if (text_range.end() - target_range.GetMax() > kExtraNumberOfChars)
text_range.set_end(target_range.GetMax() + kExtraNumberOfChars);
size_t len = text_range.length();
size_t need_size = sizeof(RECONVERTSTRING) + len * sizeof(WCHAR);
if (!reconv)
return need_size;
if (reconv->dwSize < need_size)
return 0;
base::string16 text;
if (!GetTextInputClient()->GetTextFromRange(text_range, &text))
return 0;
DCHECK_EQ(text_range.length(), text.length());
reconv->dwVersion = 0;
reconv->dwStrLen = len;
reconv->dwStrOffset = sizeof(RECONVERTSTRING);
reconv->dwCompStrLen =
client->HasCompositionText() ? target_range.length() : 0;
reconv->dwCompStrOffset =
(target_range.GetMin() - text_range.start()) * sizeof(WCHAR);
reconv->dwTargetStrLen = target_range.length();
reconv->dwTargetStrOffset = reconv->dwCompStrOffset;
memcpy((char*)reconv + sizeof(RECONVERTSTRING),
text.c_str(), len * sizeof(WCHAR));
// According to Microsoft API document, IMR_RECONVERTSTRING and
// IMR_DOCUMENTFEED should return reconv, but some applications return
// need_size.
return reinterpret_cast<LRESULT>(reconv);
}
LRESULT InputMethodWin::OnReconvertString(RECONVERTSTRING* reconv) {
ui::TextInputClient* client = GetTextInputClient();
if (!client)
return 0;
// If there is a composition string already, we don't allow reconversion.
if (client->HasCompositionText())
return 0;
gfx::Range text_range;
if (!client->GetTextRange(&text_range) || text_range.is_empty())
return 0;
gfx::Range selection_range;
if (!client->GetSelectionRange(&selection_range) ||
selection_range.is_empty()) {
return 0;
}
DCHECK(text_range.Contains(selection_range));
size_t len = selection_range.length();
size_t need_size = sizeof(RECONVERTSTRING) + len * sizeof(WCHAR);
if (!reconv)
return need_size;
if (reconv->dwSize < need_size)
return 0;
// TODO(penghuang): Return some extra context to help improve IME's
// reconversion accuracy.
base::string16 text;
if (!GetTextInputClient()->GetTextFromRange(selection_range, &text))
return 0;
DCHECK_EQ(selection_range.length(), text.length());
reconv->dwVersion = 0;
reconv->dwStrLen = len;
reconv->dwStrOffset = sizeof(RECONVERTSTRING);
reconv->dwCompStrLen = len;
reconv->dwCompStrOffset = 0;
reconv->dwTargetStrLen = len;
reconv->dwTargetStrOffset = 0;
memcpy(reinterpret_cast<char*>(reconv) + sizeof(RECONVERTSTRING),
text.c_str(), len * sizeof(WCHAR));
// According to Microsoft API document, IMR_RECONVERTSTRING and
// IMR_DOCUMENTFEED should return reconv, but some applications return
// need_size.
return reinterpret_cast<LRESULT>(reconv);
}
LRESULT InputMethodWin::OnQueryCharPosition(IMECHARPOSITION* char_positon) {
if (!char_positon)
return 0;
if (char_positon->dwSize < sizeof(IMECHARPOSITION))
return 0;
ui::TextInputClient* client = GetTextInputClient();
if (!client)
return 0;
// Tentatively assume that the returned value from |client| is DIP (Density
// Independent Pixel). See the comment in text_input_client.h and
// http://crbug.com/360334.
gfx::Rect dip_rect;
if (client->HasCompositionText()) {
if (!client->GetCompositionCharacterBounds(char_positon->dwCharPos,
&dip_rect)) {
return 0;
}
} else {
// If there is no composition and the first character is queried, returns
// the caret bounds. This behavior is the same to that of RichEdit control.
if (char_positon->dwCharPos != 0)
return 0;
dip_rect = client->GetCaretBounds();
}
const gfx::Rect rect =
display::win::ScreenWin::DIPToScreenRect(toplevel_window_handle_,
dip_rect);
char_positon->pt.x = rect.x();
char_positon->pt.y = rect.y();
char_positon->cLineHeight = rect.height();
return 1; // returns non-zero value when succeeded.
}
void InputMethodWin::RefreshInputLanguage() {
TextInputType type_original = GetTextInputType();
imm32_manager_.SetInputLanguage();
......@@ -677,18 +452,6 @@ void InputMethodWin::RefreshInputLanguage() {
}
}
bool InputMethodWin::IsWindowFocused(const TextInputClient* client) const {
if (!client)
return false;
// When Aura is enabled, |attached_window_handle| should always be a top-level
// window. So we can safely assume that |attached_window_handle| is ready for
// receiving keyboard input as long as it is an active window. This works well
// even when the |attached_window_handle| becomes active but has not received
// WM_FOCUS yet.
return toplevel_window_handle_ &&
GetActiveWindow() == toplevel_window_handle_;
}
ui::EventDispatchDetails InputMethodWin::DispatchFabricatedKeyEvent(
ui::KeyEvent* event) {
// The key event if from calling input.ime.sendKeyEvent or test.
......
......@@ -11,13 +11,13 @@
#include "base/compiler_specific.h"
#include "base/macros.h"
#include "ui/base/ime/input_method_base.h"
#include "ui/base/ime/input_method_win_base.h"
#include "ui/base/ime/win/imm32_manager.h"
namespace ui {
// A common InputMethod implementation based on IMM32.
class UI_BASE_IME_EXPORT InputMethodWin : public InputMethodBase {
class UI_BASE_IME_EXPORT InputMethodWin : public InputMethodWinBase {
public:
InputMethodWin(internal::InputMethodDelegate* delegate,
HWND toplevel_window_handle);
......@@ -47,14 +47,6 @@ class UI_BASE_IME_EXPORT InputMethodWin : public InputMethodBase {
TextInputClient* focused) override;
private:
// For both WM_CHAR and WM_SYSCHAR
LRESULT OnChar(HWND window_handle,
UINT message,
WPARAM wparam,
LPARAM lparam,
const base::NativeEvent& event,
BOOL* handled);
LRESULT OnImeSetContext(HWND window_handle,
UINT message,
WPARAM wparam,
......@@ -80,23 +72,8 @@ class UI_BASE_IME_EXPORT InputMethodWin : public InputMethodBase {
LPARAM lparam,
BOOL* handled);
// Some IMEs rely on WM_IME_REQUEST message even when TSF is enabled. So
// OnImeRequest (and its actual implementations as OnDocumentFeed,
// OnReconvertString, and OnQueryCharPosition) are placed in this base class.
LRESULT OnImeRequest(UINT message,
WPARAM wparam,
LPARAM lparam,
BOOL* handled);
LRESULT OnDocumentFeed(RECONVERTSTRING* reconv);
LRESULT OnReconvertString(RECONVERTSTRING* reconv);
LRESULT OnQueryCharPosition(IMECHARPOSITION* char_positon);
void RefreshInputLanguage();
// Returns true if the Win32 native window bound to |client| is considered
// to be ready for receiving keyboard input.
bool IsWindowFocused(const TextInputClient* client) const;
ui::EventDispatchDetails DispatchFabricatedKeyEvent(ui::KeyEvent* event);
// Asks the client to confirm current composition text.
......@@ -118,21 +95,11 @@ class UI_BASE_IME_EXPORT InputMethodWin : public InputMethodBase {
// (See "ui/base/ime/win/ime_input.h" for its details.)
ui::IMM32Manager imm32_manager_;
// The toplevel window handle.
// On non-Aura environment, this value is not used and always NULL.
const HWND toplevel_window_handle_;
// The new text direction and layout alignment requested by the user by
// pressing ctrl-shift. It'll be sent to the text input client when the key
// is released.
base::i18n::TextDirection pending_requested_direction_;
// Represents if WM_CHAR[wparam=='\r'] should be dispatched to the focused
// text input client or ignored silently. This flag is introduced as a quick
// workaround against crbug.com/319100
// TODO(yukawa, IME): Figure out long-term solution.
bool accept_carriage_return_;
// True when an IME should be allowed to process key events.
bool enabled_;
......
// Copyright 2018 The Chromium 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 "ui/base/ime/input_method_win_base.h"
#include <stddef.h>
#include <stdint.h>
#include <cwctype>
#include "base/auto_reset.h"
#include "base/bind.h"
#include "base/command_line.h"
#include "ui/base/ime/ime_bridge.h"
#include "ui/base/ime/ime_engine_handler_interface.h"
#include "ui/base/ime/text_input_client.h"
#include "ui/base/ime/win/tsf_input_scope.h"
#include "ui/display/win/screen_win.h"
#include "ui/events/event.h"
#include "ui/events/event_constants.h"
#include "ui/events/event_utils.h"
#include "ui/events/keycodes/keyboard_codes.h"
#include "ui/gfx/win/hwnd_util.h"
namespace ui {
namespace {
// Extra number of chars before and after selection (or composition) range which
// is returned to IME for improving conversion accuracy.
constexpr size_t kExtraNumberOfChars = 20;
} // namespace
InputMethodWinBase::InputMethodWinBase(internal::InputMethodDelegate* delegate,
HWND toplevel_window_handle)
: InputMethodBase(delegate),
toplevel_window_handle_(toplevel_window_handle) {}
InputMethodWinBase::~InputMethodWinBase() {}
void InputMethodWinBase::OnDidChangeFocusedClient(
TextInputClient* focused_before,
TextInputClient* focused) {
if (focused_before != focused)
accept_carriage_return_ = false;
}
bool InputMethodWinBase::IsWindowFocused(const TextInputClient* client) const {
if (!client)
return false;
// When Aura is enabled, |attached_window_handle| should always be a top-level
// window. So we can safely assume that |attached_window_handle| is ready for
// receiving keyboard input as long as it is an active window. This works well
// even when the |attached_window_handle| becomes active but has not received
// WM_FOCUS yet.
return toplevel_window_handle_ &&
GetActiveWindow() == toplevel_window_handle_;
}
LRESULT InputMethodWinBase::OnChar(HWND window_handle,
UINT message,
WPARAM wparam,
LPARAM lparam,
const base::NativeEvent& event,
BOOL* handled) {
*handled = TRUE;
// We need to send character events to the focused text input client event if
// its text input type is ui::TEXT_INPUT_TYPE_NONE.
if (GetTextInputClient()) {
const base::char16 kCarriageReturn = L'\r';
const base::char16 ch = static_cast<base::char16>(wparam);
// A mask to determine the previous key state from |lparam|. The value is 1
// if the key is down before the message is sent, or it is 0 if the key is
// up.
const uint32_t kPrevKeyDownBit = 0x40000000;
if (ch == kCarriageReturn && !(lparam & kPrevKeyDownBit))
accept_carriage_return_ = true;
// Conditionally ignore '\r' events to work around https://crbug.com/319100.
// TODO(yukawa, IME): Figure out long-term solution.
if (ch != kCarriageReturn || accept_carriage_return_) {
ui::KeyEvent char_event(event);
GetTextInputClient()->InsertChar(char_event);
}
}
// Explicitly show the system menu at a good location on [Alt]+[Space].
// Note: Setting |handled| to FALSE for DefWindowProc triggering of the system
// menu causes undesirable titlebar artifacts in the classic theme.
if (message == WM_SYSCHAR && wparam == VK_SPACE)
gfx::ShowSystemMenu(window_handle);
return 0;
}
LRESULT InputMethodWinBase::OnImeRequest(UINT message,
WPARAM wparam,
LPARAM lparam,
BOOL* handled) {
*handled = FALSE;
// Should not receive WM_IME_REQUEST message, if IME is disabled.
const ui::TextInputType type = GetTextInputType();
if (type == ui::TEXT_INPUT_TYPE_NONE ||
type == ui::TEXT_INPUT_TYPE_PASSWORD) {
return 0;
}
switch (wparam) {
case IMR_RECONVERTSTRING:
*handled = TRUE;
return OnReconvertString(reinterpret_cast<RECONVERTSTRING*>(lparam));
case IMR_DOCUMENTFEED:
*handled = TRUE;
return OnDocumentFeed(reinterpret_cast<RECONVERTSTRING*>(lparam));
case IMR_QUERYCHARPOSITION:
*handled = TRUE;
return OnQueryCharPosition(reinterpret_cast<IMECHARPOSITION*>(lparam));
default:
return 0;
}
}
LRESULT InputMethodWinBase::OnDocumentFeed(RECONVERTSTRING* reconv) {
ui::TextInputClient* client = GetTextInputClient();
if (!client)
return 0;
gfx::Range text_range;
if (!client->GetTextRange(&text_range) || text_range.is_empty())
return 0;
bool result = false;
gfx::Range target_range;
if (client->HasCompositionText())
result = client->GetCompositionTextRange(&target_range);
if (!result || target_range.is_empty()) {
if (!client->GetSelectionRange(&target_range) || !target_range.IsValid()) {
return 0;
}
}
if (!text_range.Contains(target_range))
return 0;
if (target_range.GetMin() - text_range.start() > kExtraNumberOfChars)
text_range.set_start(target_range.GetMin() - kExtraNumberOfChars);
if (text_range.end() - target_range.GetMax() > kExtraNumberOfChars)
text_range.set_end(target_range.GetMax() + kExtraNumberOfChars);
size_t len = text_range.length();
size_t need_size = sizeof(RECONVERTSTRING) + len * sizeof(WCHAR);
if (!reconv)
return need_size;
if (reconv->dwSize < need_size)
return 0;
base::string16 text;
if (!GetTextInputClient()->GetTextFromRange(text_range, &text))
return 0;
DCHECK_EQ(text_range.length(), text.length());
reconv->dwVersion = 0;
reconv->dwStrLen = len;
reconv->dwStrOffset = sizeof(RECONVERTSTRING);
reconv->dwCompStrLen =
client->HasCompositionText() ? target_range.length() : 0;
reconv->dwCompStrOffset =
(target_range.GetMin() - text_range.start()) * sizeof(WCHAR);
reconv->dwTargetStrLen = target_range.length();
reconv->dwTargetStrOffset = reconv->dwCompStrOffset;
memcpy((char*)reconv + sizeof(RECONVERTSTRING), text.c_str(),
len * sizeof(WCHAR));
// According to Microsoft API document, IMR_RECONVERTSTRING and
// IMR_DOCUMENTFEED should return reconv, but some applications return
// need_size.
return reinterpret_cast<LRESULT>(reconv);
}
LRESULT InputMethodWinBase::OnReconvertString(RECONVERTSTRING* reconv) {
ui::TextInputClient* client = GetTextInputClient();
if (!client)
return 0;
// If there is a composition string already, we don't allow reconversion.
if (client->HasCompositionText())
return 0;
gfx::Range text_range;
if (!client->GetTextRange(&text_range) || text_range.is_empty())
return 0;
gfx::Range selection_range;
if (!client->GetSelectionRange(&selection_range) ||
selection_range.is_empty()) {
return 0;
}
DCHECK(text_range.Contains(selection_range));
size_t len = selection_range.length();
size_t need_size = sizeof(RECONVERTSTRING) + len * sizeof(WCHAR);
if (!reconv)
return need_size;
if (reconv->dwSize < need_size)
return 0;
// TODO(penghuang): Return some extra context to help improve IME's
// reconversion accuracy.
base::string16 text;
if (!GetTextInputClient()->GetTextFromRange(selection_range, &text))
return 0;
DCHECK_EQ(selection_range.length(), text.length());
reconv->dwVersion = 0;
reconv->dwStrLen = len;
reconv->dwStrOffset = sizeof(RECONVERTSTRING);
reconv->dwCompStrLen = len;
reconv->dwCompStrOffset = 0;
reconv->dwTargetStrLen = len;
reconv->dwTargetStrOffset = 0;
memcpy(reinterpret_cast<char*>(reconv) + sizeof(RECONVERTSTRING),
text.c_str(), len * sizeof(WCHAR));
// According to Microsoft API document, IMR_RECONVERTSTRING and
// IMR_DOCUMENTFEED should return reconv, but some applications return
// need_size.
return reinterpret_cast<LRESULT>(reconv);
}
LRESULT InputMethodWinBase::OnQueryCharPosition(IMECHARPOSITION* char_positon) {
if (!char_positon)
return 0;
if (char_positon->dwSize < sizeof(IMECHARPOSITION))
return 0;
ui::TextInputClient* client = GetTextInputClient();
if (!client)
return 0;
// Tentatively assume that the returned value from |client| is DIP (Density
// Independent Pixel). See the comment in text_input_client.h and
// http://crbug.com/360334.
gfx::Rect dip_rect;
if (client->HasCompositionText()) {
if (!client->GetCompositionCharacterBounds(char_positon->dwCharPos,
&dip_rect)) {
return 0;
}
} else {
// If there is no composition and the first character is queried, returns
// the caret bounds. This behavior is the same to that of RichEdit control.
if (char_positon->dwCharPos != 0)
return 0;
dip_rect = client->GetCaretBounds();
}
const gfx::Rect rect = display::win::ScreenWin::DIPToScreenRect(
toplevel_window_handle_, dip_rect);
char_positon->pt.x = rect.x();
char_positon->pt.y = rect.y();
char_positon->cLineHeight = rect.height();
return 1; // returns non-zero value when succeeded.
}
} // namespace ui
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef UI_BASE_IME_INPUT_METHOD_WIN_BASE_H_
#define UI_BASE_IME_INPUT_METHOD_WIN_BASE_H_
#include <windows.h>
#include <string>
#include "base/compiler_specific.h"
#include "base/macros.h"
#include "ui/base/ime/input_method_base.h"
#include "ui/base/ime/win/imm32_manager.h"
namespace ui {
// A common InputMethod base implementation for Windows.
class UI_BASE_IME_EXPORT InputMethodWinBase : public InputMethodBase {
public:
InputMethodWinBase(internal::InputMethodDelegate* delegate,
HWND toplevel_window_handle);
~InputMethodWinBase() override;
protected:
void OnDidChangeFocusedClient(TextInputClient* focused_before,
TextInputClient* focused) override;
// Returns true if the Win32 native window bound to |client| is considered
// to be ready for receiving keyboard input.
bool IsWindowFocused(const TextInputClient* client) const;
// For both WM_CHAR and WM_SYSCHAR
LRESULT OnChar(HWND window_handle,
UINT message,
WPARAM wparam,
LPARAM lparam,
const base::NativeEvent& event,
BOOL* handled);
// Some IMEs rely on WM_IME_REQUEST message even when TSF is enabled. So
// OnImeRequest (and its actual implementations as OnDocumentFeed,
// OnReconvertString, and OnQueryCharPosition) are placed in this base class.
LRESULT OnImeRequest(UINT message,
WPARAM wparam,
LPARAM lparam,
BOOL* handled);
LRESULT OnDocumentFeed(RECONVERTSTRING* reconv);
LRESULT OnReconvertString(RECONVERTSTRING* reconv);
LRESULT OnQueryCharPosition(IMECHARPOSITION* char_positon);
// The toplevel window handle.
const HWND toplevel_window_handle_;
// Represents if WM_CHAR[wparam=='\r'] should be dispatched to the focused
// text input client or ignored silently. This flag is introduced as a quick
// workaround against https://crbug.com/319100
// TODO(yukawa, IME): Figure out long-term solution.
bool accept_carriage_return_ = false;
DISALLOW_COPY_AND_ASSIGN(InputMethodWinBase);
};
} // namespace ui
#endif // UI_BASE_IME_INPUT_METHOD_WIN_BASE_H_
// Copyright 2018 The Chromium 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 "ui/base/ime/input_method_win_tsf.h"
#include "ui/base/ime/text_input_client.h"
#include "ui/base/ime/win/tsf_bridge.h"
#include "ui/base/ime/win/tsf_event_router.h"
namespace ui {
class InputMethodWinTSF::TSFEventObserver : public TSFEventRouterObserver {
public:
TSFEventObserver() = default;
// Returns true if we know for sure that a candidate window (or IME suggest,
// etc.) is open.
bool IsCandidatePopupOpen() const { return is_candidate_popup_open_; }
// Overridden from TSFEventRouterObserver:
void OnCandidateWindowCountChanged(size_t window_count) override {
is_candidate_popup_open_ = (window_count != 0);
}
private:
// True if we know for sure that a candidate window is open.
bool is_candidate_popup_open_ = false;
DISALLOW_COPY_AND_ASSIGN(TSFEventObserver);
};
InputMethodWinTSF::InputMethodWinTSF(internal::InputMethodDelegate* delegate,
HWND toplevel_window_handle)
: InputMethodWinBase(delegate, toplevel_window_handle),
tsf_event_observer_(new TSFEventObserver()),
tsf_event_router_(new TSFEventRouter(tsf_event_observer_.get())) {}
InputMethodWinTSF::~InputMethodWinTSF() {}
ui::EventDispatchDetails InputMethodWinTSF::DispatchKeyEvent(
ui::KeyEvent* event) {
// TODO(dtapuska): Handle WM_CHAR events.
return ui::EventDispatchDetails();
}
void InputMethodWinTSF::OnFocus() {
tsf_event_router_->SetManager(
ui::TSFBridge::GetInstance()->GetThreadManager().Get());
}
void InputMethodWinTSF::OnBlur() {
tsf_event_router_->SetManager(nullptr);
}
bool InputMethodWinTSF::OnUntranslatedIMEMessage(
const base::NativeEvent& event,
InputMethod::NativeEventResult* result) {
LRESULT original_result = 0;
BOOL handled = FALSE;
// Even when TSF is enabled, following IMM32/Win32 messages must be handled.
switch (event.message) {
case WM_IME_REQUEST:
// Some TSF-native TIPs (Text Input Processors) such as ATOK and Mozc
// still rely on WM_IME_REQUEST message to implement reverse conversion.
original_result =
OnImeRequest(event.message, event.wParam, event.lParam, &handled);
break;
case WM_CHAR:
case WM_SYSCHAR:
// ui::InputMethod interface is responsible for handling Win32 character
// messages. For instance, we will be here in the following cases.
// - TIP is not activated. (e.g, the current language profile is English)
// - TIP does not handle and WM_KEYDOWN and WM_KEYDOWN is translated into
// WM_CHAR by TranslateMessage API. (e.g, TIP is turned off)
// - Another application sends WM_CHAR through SendMessage API.
original_result = OnChar(event.hwnd, event.message, event.wParam,
event.lParam, event, &handled);
break;
}
if (result)
*result = original_result;
return !!handled;
}
void InputMethodWinTSF::OnTextInputTypeChanged(const TextInputClient* client) {
if (!IsTextInputClientFocused(client) || !IsWindowFocused(client))
return;
ui::TSFBridge::GetInstance()->CancelComposition();
ui::TSFBridge::GetInstance()->OnTextInputTypeChanged(client);
}
void InputMethodWinTSF::OnCaretBoundsChanged(const TextInputClient* client) {
if (!IsTextInputClientFocused(client) || !IsWindowFocused(client))
return;
ui::TSFBridge::GetInstance()->OnTextLayoutChanged();
}
void InputMethodWinTSF::CancelComposition(const TextInputClient* client) {
if (IsTextInputClientFocused(client) && IsWindowFocused(client))
ui::TSFBridge::GetInstance()->CancelComposition();
}
void InputMethodWinTSF::DetachTextInputClient(TextInputClient* client) {
InputMethodWinBase::DetachTextInputClient(client);
ui::TSFBridge::GetInstance()->RemoveFocusedClient(client);
}
bool InputMethodWinTSF::IsCandidatePopupOpen() const {
return tsf_event_observer_->IsCandidatePopupOpen();
}
void InputMethodWinTSF::OnWillChangeFocusedClient(
TextInputClient* focused_before,
TextInputClient* focused) {
if (IsWindowFocused(focused_before)) {
ConfirmCompositionText();
ui::TSFBridge::GetInstance()->RemoveFocusedClient(focused_before);
}
}
void InputMethodWinTSF::OnDidChangeFocusedClient(
TextInputClient* focused_before,
TextInputClient* focused) {
if (IsWindowFocused(focused) && IsTextInputClientFocused(focused)) {
ui::TSFBridge::GetInstance()->SetFocusedClient(toplevel_window_handle_,
focused);
// Force to update the input type since client's TextInputStateChanged()
// function might not be called if text input types before the client loses
// focus and after it acquires focus again are the same.
OnTextInputTypeChanged(focused);
// Force to update caret bounds, in case the client thinks that the caret
// bounds has not changed.
OnCaretBoundsChanged(focused);
}
InputMethodWinBase::OnDidChangeFocusedClient(focused_before, focused);
}
void InputMethodWinTSF::ConfirmCompositionText() {
if (!IsTextInputTypeNone())
ui::TSFBridge::GetInstance()->ConfirmComposition();
}
} // namespace ui
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef UI_BASE_IME_INPUT_METHOD_WIN_TSF_H_
#define UI_BASE_IME_INPUT_METHOD_WIN_TSF_H_
#include <windows.h>
#include <string>
#include "ui/base/ime/input_method_win_base.h"
namespace ui {
class TSFEventRouter;
// An InputMethod implementation based on Windows TSF API.
class UI_BASE_IME_EXPORT InputMethodWinTSF : public InputMethodWinBase {
public:
InputMethodWinTSF(internal::InputMethodDelegate* delegate,
HWND toplevel_window_handle);
~InputMethodWinTSF() override;
// Overridden from InputMethod:
ui::EventDispatchDetails DispatchKeyEvent(ui::KeyEvent* event) override;
void OnFocus() override;
void OnBlur() override;
bool OnUntranslatedIMEMessage(const base::NativeEvent& event,
NativeEventResult* result) override;
void OnTextInputTypeChanged(const TextInputClient* client) override;
void OnCaretBoundsChanged(const TextInputClient* client) override;
void CancelComposition(const TextInputClient* client) override;
void DetachTextInputClient(TextInputClient* client) override;
bool IsCandidatePopupOpen() const override;
// Overridden from InputMethodBase:
void OnWillChangeFocusedClient(TextInputClient* focused_before,
TextInputClient* focused) override;
void OnDidChangeFocusedClient(TextInputClient* focused_before,
TextInputClient* focused) override;
private:
class TSFEventObserver;
// Asks the client to confirm current composition text.
void ConfirmCompositionText();
// TSF event router and observer.
std::unique_ptr<TSFEventObserver> tsf_event_observer_;
std::unique_ptr<TSFEventRouter> tsf_event_router_;
DISALLOW_COPY_AND_ASSIGN(InputMethodWinTSF);
};
} // namespace ui
#endif // UI_BASE_IME_INPUT_METHOD_WIN_TSF_H_
// Copyright 2018 The Chromium 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 "ui/base/ime/win/mock_tsf_bridge.h"
#include "base/logging.h"
#include "ui/base/ime/text_input_client.h"
namespace ui {
MockTSFBridge::MockTSFBridge() = default;
MockTSFBridge::~MockTSFBridge() = default;
bool MockTSFBridge::CancelComposition() {
++cancel_composition_call_count_;
return true;
}
bool MockTSFBridge::ConfirmComposition() {
++confirm_composition_call_count_;
return true;
}
void MockTSFBridge::OnTextInputTypeChanged(const TextInputClient* client) {
latest_text_input_type_ = client->GetTextInputType();
}
void MockTSFBridge::OnTextLayoutChanged() {
++on_text_layout_changed_;
}
void MockTSFBridge::SetFocusedClient(HWND focused_window,
TextInputClient* client) {
++set_focused_client_call_count_;
focused_window_ = focused_window;
text_input_client_ = client;
}
void MockTSFBridge::RemoveFocusedClient(TextInputClient* client) {
++remove_focused_client_call_count_;
DCHECK_EQ(client, text_input_client_);
text_input_client_ = nullptr;
focused_window_ = nullptr;
}
Microsoft::WRL::ComPtr<ITfThreadMgr> MockTSFBridge::GetThreadManager() {
return thread_manager_;
}
TextInputClient* MockTSFBridge::GetFocusedTextInputClient() const {
return text_input_client_;
}
void MockTSFBridge::Reset() {
enable_ime_call_count_ = 0;
disable_ime_call_count_ = 0;
cancel_composition_call_count_ = 0;
confirm_composition_call_count_ = 0;
on_text_layout_changed_ = 0;
associate_focus_call_count_ = 0;
set_focused_client_call_count_ = 0;
remove_focused_client_call_count_ = 0;
text_input_client_ = nullptr;
focused_window_ = nullptr;
latest_text_input_type_ = TEXT_INPUT_TYPE_NONE;
}
} // namespace ui
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef UI_BASE_IME_WIN_MOCK_TSF_BRIDGE_H_
#define UI_BASE_IME_WIN_MOCK_TSF_BRIDGE_H_
#include <msctf.h>
#include <wrl/client.h>
#include "base/compiler_specific.h"
#include "ui/base/ime/text_input_type.h"
#include "ui/base/ime/win/tsf_bridge.h"
namespace ui {
class MockTSFBridge : public TSFBridge {
public:
MockTSFBridge();
~MockTSFBridge() override;
// TSFBridge:
bool CancelComposition() override;
bool ConfirmComposition() override;
void OnTextInputTypeChanged(const TextInputClient* client) override;
void OnTextLayoutChanged() override;
void SetFocusedClient(HWND focused_window, TextInputClient* client) override;
void RemoveFocusedClient(TextInputClient* client) override;
Microsoft::WRL::ComPtr<ITfThreadMgr> GetThreadManager() override;
TextInputClient* GetFocusedTextInputClient() const override;
// Resets MockTSFBridge state including function call counter.
void Reset();
// Call count of EnableIME().
unsigned enable_ime_call_count() const { return enable_ime_call_count_; }
// Call count of DisableIME().
unsigned disable_ime_call_count() const { return disable_ime_call_count_; }
// Call count of CancelComposition().
unsigned cancel_composition_call_count() const {
return cancel_composition_call_count_;
}
// Call count of ConfirmComposition().
unsigned confirm_composition_call_count() const {
return confirm_composition_call_count_;
}
// Call count of OnTextLayoutChanged().
unsigned on_text_layout_changed() const { return on_text_layout_changed_; }
// Call count of AssociateFocus().
unsigned associate_focus_call_count() const {
return associate_focus_call_count_;
}
// Call count of SetFocusClient().
unsigned set_focused_client_call_count() const {
return set_focused_client_call_count_;
}
// Call count of RemoveFocusedClient().
unsigned remove_focused_client_call_count() const {
return remove_focused_client_call_count_;
}
// Returns current TextInputClient.
TextInputClient* text_input_clinet() const { return text_input_client_; }
// Returns currently focused window handle.
HWND focused_window() const { return focused_window_; }
// Returns latest text input type.
TextInputType latest_text_iput_type() const {
return latest_text_input_type_;
}
private:
unsigned enable_ime_call_count_ = 0;
unsigned disable_ime_call_count_ = 0;
unsigned cancel_composition_call_count_ = 0;
unsigned confirm_composition_call_count_ = 0;
unsigned on_text_layout_changed_ = 0;
unsigned associate_focus_call_count_ = 0;
unsigned set_focused_client_call_count_ = 0;
unsigned remove_focused_client_call_count_ = 0;
TextInputClient* text_input_client_ = nullptr;
HWND focused_window_ = nullptr;
TextInputType latest_text_input_type_ = TEXT_INPUT_TYPE_NONE;
Microsoft::WRL::ComPtr<ITfThreadMgr> thread_manager_;
DISALLOW_COPY_AND_ASSIGN(MockTSFBridge);
};
} // namespace ui
#endif // UI_BASE_IME_WIN_MOCK_TSF_BRIDGE_H_
This diff is collapsed.
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef UI_BASE_IME_WIN_TSF_BRIDGE_H_
#define UI_BASE_IME_WIN_TSF_BRIDGE_H_
#include <msctf.h>
#include <windows.h>
#include <wrl/client.h>
#include "base/macros.h"
#include "ui/base/ime/ui_base_ime_export.h"
namespace ui {
class TextInputClient;
// TSFBridge provides high level IME related operations on top of Text Services
// Framework (TSF). TSFBridge is managed by TLS because TSF related stuff is
// associated with each thread and not allowed to access across thread boundary.
// To be consistent with IMM32 behavior, TSFBridge is shared in the same thread.
// TSFBridge is used by the web content text inputting field, for example
// DisableIME() should be called if a password field is focused.
//
// TSFBridge also manages connectivity between TSFTextStore which is the backend
// of text inputting and current focused TextInputClient.
//
// All methods in this class must be used in UI thread.
class UI_BASE_IME_EXPORT TSFBridge {
public:
virtual ~TSFBridge();
// Returns the thread local TSFBridge instance. Initialize() must be called
// first. Do not cache this pointer and use it after TSFBridge Shutdown().
static TSFBridge* GetInstance();
// Sets the thread local instance. Must be called before any calls to
// GetInstance().
static void Initialize();
// Injects an alternative TSFBridge such as MockTSFBridge for testing. The
// injected object should be released by the caller. This function returns
// previous TSFBridge pointer with ownership.
static TSFBridge* ReplaceForTesting(TSFBridge* bridge);
// Destroys the thread local instance.
static void Shutdown();
// Handles TextInputTypeChanged event. RWHVW is responsible for calling this
// handler whenever renderer's input text type is changed. Does nothing
// unless |client| is focused.
virtual void OnTextInputTypeChanged(const TextInputClient* client) = 0;
// Sends an event to TSF manager that the text layout should be updated.
virtual void OnTextLayoutChanged() = 0;
// Cancels the ongoing composition if exists.
// Returns true if there is no composition.
// Returns false if an edit session is on-going.
// Returns false if an error occures.
virtual bool CancelComposition() = 0;
// Confirms the ongoing composition if exists.
// Returns true if there is no composition.
// Returns false if an edit session is on-going.
// Returns false if an error occures.
virtual bool ConfirmComposition() = 0;
// Sets currently focused TextInputClient.
// Caller must free |client|.
virtual void SetFocusedClient(HWND focused_window,
TextInputClient* client) = 0;
// Removes currently focused TextInputClient.
// Caller must free |client|.
virtual void RemoveFocusedClient(TextInputClient* client) = 0;
// Obtains current thread manager.
virtual Microsoft::WRL::ComPtr<ITfThreadMgr> GetThreadManager() = 0;
// Returns the focused text input client.
virtual TextInputClient* GetFocusedTextInputClient() const = 0;
protected:
// Uses GetInstance() instead.
TSFBridge();
private:
DISALLOW_COPY_AND_ASSIGN(TSFBridge);
};
} // namespace ui
#endif // UI_BASE_IME_WIN_TSF_BRIDGE_H_
// Copyright 2018 The Chromium 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 "ui/base/ime/win/tsf_event_router.h"
#include <msctf.h>
#include <wrl/client.h>
#include <set>
#include <utility>
#include "base/bind.h"
#include "ui/base/win/atl_module.h"
#include "ui/gfx/range/range.h"
namespace ui {
// TSFEventRouter::Delegate ------------------------------------
// The implementation class of ITfUIElementSink, whose member functions will be
// called back by TSF when the UI element status is changed, for example when
// the candidate window is opened or closed. This class also implements
// ITfTextEditSink, whose member function is called back by TSF when the text
// editting session is finished.
class ATL_NO_VTABLE TSFEventRouter::Delegate
: public ATL::CComObjectRootEx<CComSingleThreadModel>,
public ITfUIElementSink,
public ITfTextEditSink {
public:
BEGIN_COM_MAP(Delegate)
COM_INTERFACE_ENTRY(ITfUIElementSink)
COM_INTERFACE_ENTRY(ITfTextEditSink)
END_COM_MAP()
Delegate();
~Delegate();
// ITfTextEditSink:
STDMETHOD(OnEndEdit)
(ITfContext* context,
TfEditCookie read_only_cookie,
ITfEditRecord* edit_record) override;
// ITfUiElementSink:
STDMETHOD(BeginUIElement)(DWORD element_id, BOOL* is_show) override;
STDMETHOD(UpdateUIElement)(DWORD element_id) override;
STDMETHOD(EndUIElement)(DWORD element_id) override;
// Sets |thread_manager| to be monitored. |thread_manager| can be nullptr.
void SetManager(ITfThreadMgr* thread_manager);
// Returns true if the IME is composing text.
bool IsImeComposing();
// Sets |router| to be forwarded TSF-related events.
void SetRouter(TSFEventRouter* router);
private:
// Returns current composition range. Returns gfx::Range::InvalidRange if
// there is no composition.
static gfx::Range GetCompositionRange(ITfContext* context);
// Returns true if the given |element_id| represents the candidate window.
bool IsCandidateWindowInternal(DWORD element_id);
// A context associated with this class.
Microsoft::WRL::ComPtr<ITfContext> context_;
// The ITfSource associated with |context_|.
Microsoft::WRL::ComPtr<ITfSource> context_source_;
// The cookie for |context_source_|.
DWORD context_source_cookie_;
// A UIElementMgr associated with this class.
Microsoft::WRL::ComPtr<ITfUIElementMgr> ui_element_manager_;
// The ITfSouce associated with |ui_element_manager_|.
Microsoft::WRL::ComPtr<ITfSource> ui_source_;
// The set of currently opened candidate window ids.
std::set<DWORD> open_candidate_window_ids_;
// The cookie for |ui_source_|.
DWORD ui_source_cookie_ = TF_INVALID_COOKIE;
TSFEventRouter* router_ = nullptr;
gfx::Range previous_composition_range_;
DISALLOW_COPY_AND_ASSIGN(Delegate);
};
TSFEventRouter::Delegate::Delegate()
: previous_composition_range_(gfx::Range::InvalidRange()) {}
TSFEventRouter::Delegate::~Delegate() = default;
void TSFEventRouter::Delegate::SetRouter(TSFEventRouter* router) {
router_ = router;
}
STDMETHODIMP TSFEventRouter::Delegate::OnEndEdit(ITfContext* context,
TfEditCookie read_only_cookie,
ITfEditRecord* edit_record) {
if (!edit_record || !context)
return E_INVALIDARG;
if (!router_)
return S_OK;
// |edit_record| can be used to obtain updated ranges in terms of text
// contents and/or text attributes. Here we are interested only in text update
// so we use TF_GTP_INCL_TEXT and check if there is any range which contains
// updated text.
Microsoft::WRL::ComPtr<IEnumTfRanges> ranges;
if (FAILED(edit_record->GetTextAndPropertyUpdates(TF_GTP_INCL_TEXT, nullptr,
0, ranges.GetAddressOf())))
return S_OK; // Don't care about failures.
ULONG fetched_count = 0;
Microsoft::WRL::ComPtr<ITfRange> range;
if (FAILED(ranges->Next(1, range.GetAddressOf(), &fetched_count)))
return S_OK; // Don't care about failures.
const gfx::Range composition_range = GetCompositionRange(context);
if (!previous_composition_range_.IsValid() && composition_range.IsValid())
router_->OnTSFStartComposition();
// |fetched_count| != 0 means there is at least one range that contains
// updated text.
if (fetched_count != 0)
router_->OnTextUpdated(composition_range);
if (previous_composition_range_.IsValid() && !composition_range.IsValid())
router_->OnTSFEndComposition();
previous_composition_range_ = composition_range;
return S_OK;
}
STDMETHODIMP TSFEventRouter::Delegate::BeginUIElement(DWORD element_id,
BOOL* is_show) {
if (is_show)
*is_show = TRUE; // Without this the UI element will not be shown.
if (!IsCandidateWindowInternal(element_id))
return S_OK;
std::pair<std::set<DWORD>::iterator, bool> insert_result =
open_candidate_window_ids_.insert(element_id);
// Don't call if |router_| is null or |element_id| is already handled.
if (router_ && insert_result.second)
router_->OnCandidateWindowCountChanged(open_candidate_window_ids_.size());
return S_OK;
}
STDMETHODIMP TSFEventRouter::Delegate::UpdateUIElement(DWORD element_id) {
return S_OK;
}
STDMETHODIMP TSFEventRouter::Delegate::EndUIElement(DWORD element_id) {
if ((open_candidate_window_ids_.erase(element_id) != 0) && router_)
router_->OnCandidateWindowCountChanged(open_candidate_window_ids_.size());
return S_OK;
}
void TSFEventRouter::Delegate::SetManager(ITfThreadMgr* thread_manager) {
context_.Reset();
if (context_source_) {
context_source_->UnadviseSink(context_source_cookie_);
context_source_.Reset();
}
context_source_cookie_ = TF_INVALID_COOKIE;
ui_element_manager_.Reset();
if (ui_source_) {
ui_source_->UnadviseSink(ui_source_cookie_);
ui_source_.Reset();
}
ui_source_cookie_ = TF_INVALID_COOKIE;
if (!thread_manager)
return;
Microsoft::WRL::ComPtr<ITfDocumentMgr> document_manager;
if (FAILED(thread_manager->GetFocus(document_manager.GetAddressOf())) ||
!document_manager.Get() ||
FAILED(document_manager->GetBase(context_.GetAddressOf())) ||
FAILED(context_.CopyTo(context_source_.GetAddressOf())))
return;
context_source_->AdviseSink(IID_ITfTextEditSink,
static_cast<ITfTextEditSink*>(this),
&context_source_cookie_);
if (FAILED(
thread_manager->QueryInterface(IID_PPV_ARGS(&ui_element_manager_))) ||
FAILED(ui_element_manager_.CopyTo(ui_source_.GetAddressOf())))
return;
ui_source_->AdviseSink(IID_ITfUIElementSink,
static_cast<ITfUIElementSink*>(this),
&ui_source_cookie_);
}
bool TSFEventRouter::Delegate::IsImeComposing() {
return context_ && GetCompositionRange(context_.Get()).IsValid();
}
// static
gfx::Range TSFEventRouter::Delegate::GetCompositionRange(ITfContext* context) {
DCHECK(context);
Microsoft::WRL::ComPtr<ITfContextComposition> context_composition;
if (FAILED(context->QueryInterface(IID_PPV_ARGS(&context_composition))))
return gfx::Range::InvalidRange();
Microsoft::WRL::ComPtr<IEnumITfCompositionView> enum_composition_view;
if (FAILED(context_composition->EnumCompositions(
enum_composition_view.GetAddressOf())))
return gfx::Range::InvalidRange();
Microsoft::WRL::ComPtr<ITfCompositionView> composition_view;
if (enum_composition_view->Next(1, composition_view.GetAddressOf(),
nullptr) != S_OK)
return gfx::Range::InvalidRange();
Microsoft::WRL::ComPtr<ITfRange> range;
if (FAILED(composition_view->GetRange(range.GetAddressOf())))
return gfx::Range::InvalidRange();
Microsoft::WRL::ComPtr<ITfRangeACP> range_acp;
if (FAILED(range.CopyTo(range_acp.GetAddressOf())))
return gfx::Range::InvalidRange();
LONG start = 0;
LONG length = 0;
if (FAILED(range_acp->GetExtent(&start, &length)))
return gfx::Range::InvalidRange();
return gfx::Range(start, start + length);
}
bool TSFEventRouter::Delegate::IsCandidateWindowInternal(DWORD element_id) {
DCHECK(ui_element_manager_.Get());
Microsoft::WRL::ComPtr<ITfUIElement> ui_element;
if (FAILED(ui_element_manager_->GetUIElement(element_id,
ui_element.GetAddressOf())))
return false;
Microsoft::WRL::ComPtr<ITfCandidateListUIElement> candidate_list_ui_element;
return SUCCEEDED(ui_element.CopyTo(candidate_list_ui_element.GetAddressOf()));
}
// TSFEventRouter ------------------------------------------------------------
TSFEventRouter::TSFEventRouter(TSFEventRouterObserver* observer)
: observer_(observer) {
DCHECK(observer_);
CComObject<Delegate>* delegate;
ui::win::CreateATLModuleIfNeeded();
if (SUCCEEDED(CComObject<Delegate>::CreateInstance(&delegate))) {
delegate->AddRef();
delegate_.Attach(delegate);
delegate_->SetRouter(this);
}
}
TSFEventRouter::~TSFEventRouter() {
if (delegate_) {
delegate_->SetManager(nullptr);
delegate_->SetRouter(nullptr);
}
}
bool TSFEventRouter::IsImeComposing() {
return delegate_->IsImeComposing();
}
void TSFEventRouter::OnCandidateWindowCountChanged(size_t window_count) {
observer_->OnCandidateWindowCountChanged(window_count);
}
void TSFEventRouter::OnTSFStartComposition() {
observer_->OnTSFStartComposition();
}
void TSFEventRouter::OnTextUpdated(const gfx::Range& composition_range) {
observer_->OnTextUpdated(composition_range);
}
void TSFEventRouter::OnTSFEndComposition() {
observer_->OnTSFEndComposition();
}
void TSFEventRouter::SetManager(ITfThreadMgr* thread_manager) {
delegate_->SetManager(thread_manager);
}
} // namespace ui
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef UI_BASE_IME_WIN_TSF_EVENT_ROUTER_H_
#define UI_BASE_IME_WIN_TSF_EVENT_ROUTER_H_
#include <atlbase.h>
#include <atlcom.h>
#include <msctf.h>
#include <set>
#include "base/callback.h"
#include "base/compiler_specific.h"
#include "ui/base/ime/text_input_type.h"
#include "ui/base/ime/ui_base_ime_export.h"
#include "ui/gfx/range/range.h"
namespace ui {
class TSFEventRouterObserver {
public:
TSFEventRouterObserver() {}
// Called when the number of currently opened candidate windows changes.
virtual void OnCandidateWindowCountChanged(size_t window_count) {}
// Called when a composition is started.
virtual void OnTSFStartComposition() {}
// Called when the text contents are updated. If there is no composition,
// gfx::Range::InvalidRange is passed to |composition_range|.
virtual void OnTextUpdated(const gfx::Range& composition_range) {}
// Called when a composition is terminated.
virtual void OnTSFEndComposition() {}
protected:
virtual ~TSFEventRouterObserver() {}
private:
DISALLOW_COPY_AND_ASSIGN(TSFEventRouterObserver);
};
// This class monitors TSF related events and forwards them to given
// |observer|.
class UI_BASE_IME_EXPORT TSFEventRouter {
public:
// Do not pass NULL to |observer|.
explicit TSFEventRouter(TSFEventRouterObserver* observer);
virtual ~TSFEventRouter();
// Returns true if the IME is composing text.
bool IsImeComposing();
// Callbacks from the TSFEventRouterDelegate:
void OnCandidateWindowCountChanged(size_t window_count);
void OnTSFStartComposition();
void OnTextUpdated(const gfx::Range& composition_range);
void OnTSFEndComposition();
// Sets |thread_manager| to be monitored. |thread_manager| can be NULL.
void SetManager(ITfThreadMgr* thread_manager);
private:
class Delegate;
CComPtr<Delegate> delegate_;
TSFEventRouterObserver* observer_;
DISALLOW_COPY_AND_ASSIGN(TSFEventRouter);
};
} // namespace ui
#endif // UI_BASE_IME_WIN_TSF_EVENT_ROUTER_H_
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
......@@ -45,6 +45,9 @@ const base::Feature kDirectManipulationStylus = {
// Enables using WM_POINTER instead of WM_TOUCH for touch events.
const base::Feature kPointerEventsForTouch = {"PointerEventsForTouch",
base::FEATURE_ENABLED_BY_DEFAULT};
// Enables using TSF (over IMM32) for IME.
const base::Feature kTSFImeSupport = {"TSFImeSupport",
base::FEATURE_DISABLED_BY_DEFAULT};
bool IsUsingWMPointerForTouch() {
return base::win::GetVersion() >= base::win::VERSION_WIN8 &&
......
......@@ -23,6 +23,7 @@ UI_BASE_EXPORT bool IsTouchableAppContextMenuEnabled();
UI_BASE_EXPORT extern const base::Feature kDirectManipulationStylus;
UI_BASE_EXPORT extern const base::Feature kPointerEventsForTouch;
UI_BASE_EXPORT extern const base::Feature kPrecisionTouchpad;
UI_BASE_EXPORT extern const base::Feature kTSFImeSupport;
// Returns true if the system should use WM_POINTER events for touch events.
UI_BASE_EXPORT bool IsUsingWMPointerForTouch();
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment