| Index: win8/metro_driver/ime/text_service.cc
|
| diff --git a/win8/metro_driver/ime/text_service.cc b/win8/metro_driver/ime/text_service.cc
|
| deleted file mode 100644
|
| index 1ee845e024ac57e24f2585d358ce5010729af913..0000000000000000000000000000000000000000
|
| --- a/win8/metro_driver/ime/text_service.cc
|
| +++ /dev/null
|
| @@ -1,490 +0,0 @@
|
| -// Copyright 2013 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 "win8/metro_driver/ime/text_service.h"
|
| -
|
| -#include <msctf.h>
|
| -#include <stddef.h>
|
| -#include <stdint.h>
|
| -
|
| -#include <utility>
|
| -
|
| -#include "base/logging.h"
|
| -#include "base/macros.h"
|
| -#include "base/win/scoped_variant.h"
|
| -#include "ui/metro_viewer/ime_types.h"
|
| -#include "win8/metro_driver/ime/text_service_delegate.h"
|
| -#include "win8/metro_driver/ime/text_store.h"
|
| -#include "win8/metro_driver/ime/text_store_delegate.h"
|
| -
|
| -// Architecture overview of input method support on Ash mode:
|
| -//
|
| -// Overview:
|
| -// On Ash mode, the system keyboard focus is owned by the metro_driver process
|
| -// while most of event handling are still implemented in the browser process.
|
| -// Thus the metro_driver basically works as a proxy that simply forwards
|
| -// keyevents to the metro_driver process. IME support must be involved somewhere
|
| -// in this flow.
|
| -//
|
| -// In short, we need to interact with an IME in the metro_driver process since
|
| -// TSF (Text Services Framework) runtime wants to processes keyevents while
|
| -// (and only while) the attached UI thread owns keyboard focus.
|
| -//
|
| -// Due to this limitation, we need to split IME handling into two parts, one
|
| -// is in the metro_driver process and the other is in the browser process.
|
| -// The metro_driver process is responsible for implementing the primary data
|
| -// store for the composition text and wiring it up with an IME via TSF APIs.
|
| -// On the other hand, the browser process is responsible for calculating
|
| -// character position in the composition text whenever the composition text
|
| -// is updated.
|
| -//
|
| -// IPC overview:
|
| -// Fortunately, we don't need so many IPC messages to support IMEs. In fact,
|
| -// only 4 messages are required to enable basic IME functionality.
|
| -//
|
| -// metro_driver process -> browser process
|
| -// Message Type:
|
| -// - MetroViewerHostMsg_ImeCompositionChanged
|
| -// - MetroViewerHostMsg_ImeTextCommitted
|
| -// Message Routing:
|
| -// TextServiceImpl
|
| -// -> ChromeAppViewAsh
|
| -// -- (process boundary) --
|
| -// -> RemoteWindowTreeHostWin
|
| -// -> RemoteInputMethodWin
|
| -//
|
| -// browser process -> metro_driver process
|
| -// Message Type:
|
| -// - MetroViewerHostMsg_ImeCancelComposition
|
| -// - MetroViewerHostMsg_ImeTextInputClientUpdated
|
| -// Message Routing:
|
| -// RemoteInputMethodWin
|
| -// -> RemoteWindowTreeHostWin
|
| -// -- (process boundary) --
|
| -// -> ChromeAppViewAsh
|
| -// -> TextServiceImpl
|
| -//
|
| -// Note that a keyevent may be forwarded through a different path. When a
|
| -// keyevent is not handled by an IME, such keyevent and subsequent character
|
| -// events will be sent from the metro_driver process to the browser process as
|
| -// following IPC messages.
|
| -// - MetroViewerHostMsg_KeyDown
|
| -// - MetroViewerHostMsg_KeyUp
|
| -// - MetroViewerHostMsg_Character
|
| -//
|
| -// How TextServiceImpl works:
|
| -// Here is the list of the major tasks that are handled in TextServiceImpl.
|
| -// - Manages a session object obtained from TSF runtime. We need them to call
|
| -// most of TSF APIs.
|
| -// - Handles OnDocumentChanged event. Whenever the document type is changed,
|
| -// TextServiceImpl destroyes the current document and initializes new one
|
| -// according to the given |input_scopes|.
|
| -// - Stores the |composition_character_bounds_| passed from OnDocumentChanged
|
| -// event so that an IME or on-screen keyboard can query the character
|
| -// position synchronously.
|
| -// The most complicated part is the OnDocumentChanged handler. Since some IMEs
|
| -// such as Japanese IMEs drastically change their behavior depending on
|
| -// properties exposed from the virtual document, we need to set up a lot
|
| -// properties carefully and correctly. See DocumentBinding class in this file
|
| -// about what will be involved in this multi-phase construction. See also
|
| -// text_store.cc and input_scope.cc for more underlying details.
|
| -
|
| -namespace metro_driver {
|
| -namespace {
|
| -
|
| -// Japanese IME expects the default value of this compartment is
|
| -// TF_SENTENCEMODE_PHRASEPREDICT to emulate IMM32 behavior. This value is
|
| -// managed per thread, thus setting this value at once is sufficient. This
|
| -// value never affects non-Japanese IMEs.
|
| -bool InitializeSentenceMode(ITfThreadMgr* thread_manager,
|
| - TfClientId client_id) {
|
| - base::win::ScopedComPtr<ITfCompartmentMgr> thread_compartment_manager;
|
| - HRESULT hr = thread_compartment_manager.QueryFrom(thread_manager);
|
| - if (FAILED(hr)) {
|
| - LOG(ERROR) << "QueryFrom failed. hr = " << hr;
|
| - return false;
|
| - }
|
| - base::win::ScopedComPtr<ITfCompartment> sentence_compartment;
|
| - hr = thread_compartment_manager->GetCompartment(
|
| - GUID_COMPARTMENT_KEYBOARD_INPUTMODE_SENTENCE,
|
| - sentence_compartment.Receive());
|
| - if (FAILED(hr)) {
|
| - LOG(ERROR) << "ITfCompartment::GetCompartment failed. hr = " << hr;
|
| - return false;
|
| - }
|
| -
|
| - base::win::ScopedVariant sentence_variant;
|
| - sentence_variant.Set(TF_SENTENCEMODE_PHRASEPREDICT);
|
| - hr = sentence_compartment->SetValue(client_id, sentence_variant.ptr());
|
| - if (FAILED(hr)) {
|
| - LOG(ERROR) << "ITfCompartment::SetValue failed. hr = " << hr;
|
| - return false;
|
| - }
|
| - return true;
|
| -}
|
| -
|
| -// Initializes |context| as disabled context where IMEs will be disabled.
|
| -bool InitializeDisabledContext(ITfContext* context, TfClientId client_id) {
|
| - base::win::ScopedComPtr<ITfCompartmentMgr> compartment_mgr;
|
| - HRESULT hr = compartment_mgr.QueryFrom(context);
|
| - if (FAILED(hr)) {
|
| - LOG(ERROR) << "QueryFrom failed. hr = " << hr;
|
| - return false;
|
| - }
|
| -
|
| - base::win::ScopedComPtr<ITfCompartment> disabled_compartment;
|
| - hr = compartment_mgr->GetCompartment(GUID_COMPARTMENT_KEYBOARD_DISABLED,
|
| - disabled_compartment.Receive());
|
| - if (FAILED(hr)) {
|
| - LOG(ERROR) << "ITfCompartment::GetCompartment failed. hr = " << hr;
|
| - return false;
|
| - }
|
| -
|
| - base::win::ScopedVariant variant;
|
| - variant.Set(1);
|
| - hr = disabled_compartment->SetValue(client_id, variant.ptr());
|
| - if (FAILED(hr)) {
|
| - LOG(ERROR) << "ITfCompartment::SetValue failed. hr = " << hr;
|
| - return false;
|
| - }
|
| -
|
| - base::win::ScopedComPtr<ITfCompartment> empty_context;
|
| - hr = compartment_mgr->GetCompartment(GUID_COMPARTMENT_EMPTYCONTEXT,
|
| - empty_context.Receive());
|
| - if (FAILED(hr)) {
|
| - LOG(ERROR) << "ITfCompartment::GetCompartment failed. hr = " << hr;
|
| - return false;
|
| - }
|
| -
|
| - base::win::ScopedVariant empty_context_variant;
|
| - empty_context_variant.Set(static_cast<int32_t>(1));
|
| - hr = empty_context->SetValue(client_id, empty_context_variant.ptr());
|
| - if (FAILED(hr)) {
|
| - LOG(ERROR) << "ITfCompartment::SetValue failed. hr = " << hr;
|
| - return false;
|
| - }
|
| -
|
| - return true;
|
| -}
|
| -
|
| -bool IsPasswordField(const std::vector<InputScope>& input_scopes) {
|
| - return std::find(input_scopes.begin(), input_scopes.end(), IS_PASSWORD) !=
|
| - input_scopes.end();
|
| -}
|
| -
|
| -// A class that manages the lifetime of the event callback registration. When
|
| -// this object is destroyed, corresponding event callback will be unregistered.
|
| -class EventSink {
|
| - public:
|
| - EventSink(DWORD cookie, base::win::ScopedComPtr<ITfSource> source)
|
| - : cookie_(cookie),
|
| - source_(source) {}
|
| - ~EventSink() {
|
| - if (!source_.get() || cookie_ != TF_INVALID_COOKIE)
|
| - return;
|
| - source_->UnadviseSink(cookie_);
|
| - cookie_ = TF_INVALID_COOKIE;
|
| - source_.Release();
|
| - }
|
| -
|
| - private:
|
| - DWORD cookie_;
|
| - base::win::ScopedComPtr<ITfSource> source_;
|
| - DISALLOW_COPY_AND_ASSIGN(EventSink);
|
| -};
|
| -
|
| -scoped_ptr<EventSink> CreateTextEditSink(ITfContext* context,
|
| - ITfTextEditSink* text_store) {
|
| - DCHECK(text_store);
|
| - base::win::ScopedComPtr<ITfSource> source;
|
| - DWORD cookie = TF_INVALID_EDIT_COOKIE;
|
| - HRESULT hr = source.QueryFrom(context);
|
| - if (FAILED(hr)) {
|
| - LOG(ERROR) << "QueryFrom failed, hr = " << hr;
|
| - return scoped_ptr<EventSink>();
|
| - }
|
| - hr = source->AdviseSink(IID_ITfTextEditSink, text_store, &cookie);
|
| - if (FAILED(hr)) {
|
| - LOG(ERROR) << "AdviseSink failed, hr = " << hr;
|
| - return scoped_ptr<EventSink>();
|
| - }
|
| - return scoped_ptr<EventSink>(new EventSink(cookie, source));
|
| -}
|
| -
|
| -// A set of objects that should have the same lifetime. Following things
|
| -// are maintained.
|
| -// - TextStore: a COM object that abstracts text buffer. This object is
|
| -// actually implemented by us in text_store.cc
|
| -// - ITfDocumentMgr: a focusable unit in TSF. This object is implemented by
|
| -// TSF runtime and works as a container of TextStore.
|
| -// - EventSink: an object that ensures that the event callback between
|
| -// TSF runtime and TextStore is unregistered when this object is destroyed.
|
| -class DocumentBinding {
|
| - public:
|
| - ~DocumentBinding() {
|
| - if (!document_manager_.get())
|
| - return;
|
| - document_manager_->Pop(TF_POPF_ALL);
|
| - }
|
| -
|
| - static scoped_ptr<DocumentBinding> Create(
|
| - ITfThreadMgr* thread_manager,
|
| - TfClientId client_id,
|
| - const std::vector<InputScope>& input_scopes,
|
| - HWND window_handle,
|
| - TextStoreDelegate* delegate) {
|
| - base::win::ScopedComPtr<ITfDocumentMgr> document_manager;
|
| - HRESULT hr = thread_manager->CreateDocumentMgr(document_manager.Receive());
|
| - if (FAILED(hr)) {
|
| - LOG(ERROR) << "ITfThreadMgr::CreateDocumentMgr failed. hr = " << hr;
|
| - return scoped_ptr<DocumentBinding>();
|
| - }
|
| -
|
| - // Note: In our IPC protocol, an empty |input_scopes| is used to indicate
|
| - // that an IME must be disabled in this context. In such case, we need not
|
| - // instantiate TextStore.
|
| - const bool use_null_text_store = input_scopes.empty();
|
| -
|
| - scoped_refptr<TextStore> text_store;
|
| - if (!use_null_text_store) {
|
| - text_store = TextStore::Create(window_handle, input_scopes, delegate);
|
| - if (!text_store.get()) {
|
| - LOG(ERROR) << "Failed to create TextStore.";
|
| - return scoped_ptr<DocumentBinding>();
|
| - }
|
| - }
|
| -
|
| - base::win::ScopedComPtr<ITfContext> context;
|
| - DWORD edit_cookie = TF_INVALID_EDIT_COOKIE;
|
| - hr = document_manager->CreateContext(
|
| - client_id,
|
| - 0,
|
| - static_cast<ITextStoreACP*>(text_store.get()),
|
| - context.Receive(),
|
| - &edit_cookie);
|
| - if (FAILED(hr)) {
|
| - LOG(ERROR) << "ITfDocumentMgr::CreateContext failed. hr = " << hr;
|
| - return scoped_ptr<DocumentBinding>();
|
| - }
|
| -
|
| - // If null-TextStore is used or |input_scopes| looks like a password field,
|
| - // set special properties to tell IMEs to be disabled.
|
| - if ((use_null_text_store || IsPasswordField(input_scopes)) &&
|
| - !InitializeDisabledContext(context.get(), client_id)) {
|
| - LOG(ERROR) << "InitializeDisabledContext failed.";
|
| - return scoped_ptr<DocumentBinding>();
|
| - }
|
| -
|
| - scoped_ptr<EventSink> text_edit_sink;
|
| - if (!use_null_text_store) {
|
| - text_edit_sink = CreateTextEditSink(context.get(), text_store.get());
|
| - if (!text_edit_sink) {
|
| - LOG(ERROR) << "CreateTextEditSink failed.";
|
| - return scoped_ptr<DocumentBinding>();
|
| - }
|
| - }
|
| - hr = document_manager->Push(context.get());
|
| - if (FAILED(hr)) {
|
| - LOG(ERROR) << "ITfDocumentMgr::Push failed. hr = " << hr;
|
| - return scoped_ptr<DocumentBinding>();
|
| - }
|
| - return scoped_ptr<DocumentBinding>(new DocumentBinding(
|
| - text_store, document_manager, std::move(text_edit_sink)));
|
| - }
|
| -
|
| - ITfDocumentMgr* document_manager() const { return document_manager_.get(); }
|
| -
|
| - scoped_refptr<TextStore> text_store() const {
|
| - return text_store_;
|
| - }
|
| -
|
| - private:
|
| - DocumentBinding(scoped_refptr<TextStore> text_store,
|
| - base::win::ScopedComPtr<ITfDocumentMgr> document_manager,
|
| - scoped_ptr<EventSink> text_edit_sink)
|
| - : text_store_(text_store),
|
| - document_manager_(document_manager),
|
| - text_edit_sink_(std::move(text_edit_sink)) {}
|
| -
|
| - scoped_refptr<TextStore> text_store_;
|
| - base::win::ScopedComPtr<ITfDocumentMgr> document_manager_;
|
| - scoped_ptr<EventSink> text_edit_sink_;
|
| -
|
| - DISALLOW_COPY_AND_ASSIGN(DocumentBinding);
|
| -};
|
| -
|
| -class TextServiceImpl : public TextService,
|
| - public TextStoreDelegate {
|
| - public:
|
| - TextServiceImpl(ITfThreadMgr* thread_manager,
|
| - TfClientId client_id,
|
| - HWND window_handle,
|
| - TextServiceDelegate* delegate)
|
| - : client_id_(client_id),
|
| - window_handle_(window_handle),
|
| - delegate_(delegate),
|
| - thread_manager_(thread_manager) {
|
| - DCHECK_NE(TF_CLIENTID_NULL, client_id);
|
| - DCHECK(window_handle != NULL);
|
| - DCHECK(thread_manager_.get());
|
| - }
|
| - ~TextServiceImpl() override {
|
| - thread_manager_->Deactivate();
|
| - }
|
| -
|
| - private:
|
| - // TextService overrides:
|
| - void CancelComposition() override {
|
| - if (!current_document_) {
|
| - VLOG(0) << "|current_document_| is NULL due to the previous error.";
|
| - return;
|
| - }
|
| - scoped_refptr<TextStore> text_store = current_document_->text_store();
|
| - if (!text_store.get())
|
| - return;
|
| - text_store->CancelComposition();
|
| - }
|
| -
|
| - void OnDocumentChanged(const std::vector<int32_t>& input_scopes,
|
| - const std::vector<metro_viewer::CharacterBounds>&
|
| - character_bounds) override {
|
| - bool document_type_changed = input_scopes_ != input_scopes;
|
| - input_scopes_ = input_scopes;
|
| - composition_character_bounds_ = character_bounds;
|
| - if (document_type_changed)
|
| - OnDocumentTypeChanged(input_scopes);
|
| - }
|
| -
|
| - void OnWindowActivated() override {
|
| - if (!current_document_) {
|
| - VLOG(0) << "|current_document_| is NULL due to the previous error.";
|
| - return;
|
| - }
|
| - ITfDocumentMgr* document_manager = current_document_->document_manager();
|
| - if (!document_manager) {
|
| - VLOG(0) << "|document_manager| is NULL due to the previous error.";
|
| - return;
|
| - }
|
| - HRESULT hr = thread_manager_->SetFocus(document_manager);
|
| - if (FAILED(hr)) {
|
| - LOG(ERROR) << "ITfThreadMgr::SetFocus failed. hr = " << hr;
|
| - return;
|
| - }
|
| - }
|
| -
|
| - void OnCompositionChanged(
|
| - const base::string16& text,
|
| - int32_t selection_start,
|
| - int32_t selection_end,
|
| - const std::vector<metro_viewer::UnderlineInfo>& underlines) override {
|
| - if (!delegate_)
|
| - return;
|
| - delegate_->OnCompositionChanged(text,
|
| - selection_start,
|
| - selection_end,
|
| - underlines);
|
| - }
|
| -
|
| - void OnTextCommitted(const base::string16& text) override {
|
| - if (!delegate_)
|
| - return;
|
| - delegate_->OnTextCommitted(text);
|
| - }
|
| -
|
| - RECT GetCaretBounds() override {
|
| - if (composition_character_bounds_.empty()) {
|
| - const RECT rect = {};
|
| - return rect;
|
| - }
|
| - const metro_viewer::CharacterBounds& bounds =
|
| - composition_character_bounds_[0];
|
| - POINT left_top = { bounds.left, bounds.top };
|
| - POINT right_bottom = { bounds.right, bounds.bottom };
|
| - ClientToScreen(window_handle_, &left_top);
|
| - ClientToScreen(window_handle_, &right_bottom);
|
| - const RECT rect = {
|
| - left_top.x,
|
| - left_top.y,
|
| - right_bottom.x,
|
| - right_bottom.y,
|
| - };
|
| - return rect;
|
| - }
|
| -
|
| - bool GetCompositionCharacterBounds(uint32_t index, RECT* rect) override {
|
| - if (index >= composition_character_bounds_.size()) {
|
| - return false;
|
| - }
|
| - const metro_viewer::CharacterBounds& bounds =
|
| - composition_character_bounds_[index];
|
| - POINT left_top = { bounds.left, bounds.top };
|
| - POINT right_bottom = { bounds.right, bounds.bottom };
|
| - ClientToScreen(window_handle_, &left_top);
|
| - ClientToScreen(window_handle_, &right_bottom);
|
| - SetRect(rect, left_top.x, left_top.y, right_bottom.x, right_bottom.y);
|
| - return true;
|
| - }
|
| -
|
| - void OnDocumentTypeChanged(const std::vector<int32_t>& input_scopes) {
|
| - std::vector<InputScope> native_input_scopes(input_scopes.size());
|
| - for (size_t i = 0; i < input_scopes.size(); ++i)
|
| - native_input_scopes[i] = static_cast<InputScope>(input_scopes[i]);
|
| - scoped_ptr<DocumentBinding> new_document =
|
| - DocumentBinding::Create(thread_manager_.get(),
|
| - client_id_,
|
| - native_input_scopes,
|
| - window_handle_,
|
| - this);
|
| - LOG_IF(ERROR, !new_document) << "Failed to create a new document.";
|
| - current_document_.swap(new_document);
|
| - OnWindowActivated();
|
| - }
|
| -
|
| - TfClientId client_id_;
|
| - HWND window_handle_;
|
| - TextServiceDelegate* delegate_;
|
| - scoped_ptr<DocumentBinding> current_document_;
|
| - base::win::ScopedComPtr<ITfThreadMgr> thread_manager_;
|
| -
|
| - // A vector of InputScope enumeration, which represents the document type of
|
| - // the focused text field. Note that in our IPC message protocol, an empty
|
| - // |input_scopes_| has special meaning that IMEs must be disabled on this
|
| - // document.
|
| - std::vector<int32_t> input_scopes_;
|
| - // Character bounds of the composition. When there is no composition but this
|
| - // vector is not empty, the first element contains the caret bounds.
|
| - std::vector<metro_viewer::CharacterBounds> composition_character_bounds_;
|
| -
|
| - DISALLOW_COPY_AND_ASSIGN(TextServiceImpl);
|
| -};
|
| -
|
| -} // namespace
|
| -
|
| -scoped_ptr<TextService>
|
| -CreateTextService(TextServiceDelegate* delegate, HWND window_handle) {
|
| - if (!delegate)
|
| - return scoped_ptr<TextService>();
|
| - base::win::ScopedComPtr<ITfThreadMgr> thread_manager;
|
| - HRESULT hr = thread_manager.CreateInstance(CLSID_TF_ThreadMgr);
|
| - if (FAILED(hr)) {
|
| - LOG(ERROR) << "Failed to create instance of CLSID_TF_ThreadMgr. hr = "
|
| - << hr;
|
| - return scoped_ptr<TextService>();
|
| - }
|
| - TfClientId client_id = TF_CLIENTID_NULL;
|
| - hr = thread_manager->Activate(&client_id);
|
| - if (FAILED(hr)) {
|
| - LOG(ERROR) << "ITfThreadMgr::Activate failed. hr = " << hr;
|
| - return scoped_ptr<TextService>();
|
| - }
|
| - if (!InitializeSentenceMode(thread_manager.get(), client_id)) {
|
| - LOG(ERROR) << "InitializeSentenceMode failed.";
|
| - thread_manager->Deactivate();
|
| - return scoped_ptr<TextService>();
|
| - }
|
| - return scoped_ptr<TextService>(new TextServiceImpl(
|
| - thread_manager.get(), client_id, window_handle, delegate));
|
| -}
|
| -
|
| -} // namespace metro_driver
|
|
|