| // Copyright (c) 2012 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 <msctf.h> |
| #include "base/logging.h" |
| #include "base/memory/ref_counted.h" |
| #include "base/memory/scoped_ptr.h" |
| #include "base/memory/singleton.h" |
| #include "base/message_loop.h" |
| #include "base/threading/thread_local_storage.h" |
| #include "base/win/scoped_comptr.h" |
| #include "base/win/scoped_variant.h" |
| #include "ui/base/ime/text_input_client.h" |
| #include "ui/base/win/tsf_bridge.h" |
| #include "ui/base/win/tsf_text_store.h" |
| |
| namespace ui { |
| |
| namespace { |
| |
| // A TLS implementation of TsfBridge. |
| class TsfBridgeDelegate : public TsfBridge { |
| public: |
| TsfBridgeDelegate() |
| : source_cookie_(TF_INVALID_COOKIE), |
| client_id_(TF_CLIENTID_NULL), |
| client_(NULL) { |
| } |
| |
| virtual ~TsfBridgeDelegate() { |
| } |
| |
| // TsfBridge override. |
| bool Initialize() { |
| DCHECK_EQ(MessageLoop::TYPE_UI, MessageLoop::current()->type()); |
| if (client_id_ != TF_CLIENTID_NULL) { |
| VLOG(1) << "Already initialized."; |
| return false; |
| } |
| |
| if (FAILED(thread_manager_.CreateInstance(CLSID_TF_ThreadMgr))) { |
| VLOG(1) << "Failed to create ThreadManager instance."; |
| return false; |
| } |
| |
| if (FAILED(thread_manager_->Activate(&client_id_))) { |
| VLOG(1) << "Failed to activate Thread Manager."; |
| return false; |
| } |
| |
| if (!InitializeForEnabledDocumentManager()) |
| return false; |
| |
| if (!InitializeForPasswordDocumentManager()) |
| return false; |
| |
| if (!InitializeForDisabledDocumentManager()) |
| return false; |
| |
| // Japanese IME expectes the default value of this compartment is |
| // TF_SENTENCEMODE_PHRASEPREDICT like IMM32 implementation. This value is |
| // managed per thread, so that it is enough to set this value at once. This |
| // value does not affect other language's IME behaviors. |
| base::win::ScopedComPtr<ITfCompartmentMgr> thread_compartment_manager; |
| if (FAILED(thread_compartment_manager.QueryFrom(thread_manager_))) { |
| VLOG(1) << "Failed to get ITfCompartmentMgr."; |
| return false; |
| } |
| |
| base::win::ScopedComPtr<ITfCompartment> sentence_compartment; |
| if (FAILED(thread_compartment_manager->GetCompartment( |
| GUID_COMPARTMENT_KEYBOARD_INPUTMODE_SENTENCE, |
| sentence_compartment.Receive()))) { |
| VLOG(1) << "Failed to get sentence compartment."; |
| return false; |
| } |
| |
| base::win::ScopedVariant sentence_variant; |
| sentence_variant.Set(TF_SENTENCEMODE_PHRASEPREDICT); |
| if (FAILED(sentence_compartment->SetValue(client_id_, &sentence_variant))) { |
| VLOG(1) << "Failed to change the sentence mode."; |
| return false; |
| } |
| |
| return true; |
| } |
| |
| // TsfBridge override. |
| virtual void Shutdown() OVERRIDE { |
| DCHECK_EQ(MessageLoop::TYPE_UI, MessageLoop::current()->type()); |
| if (!IsInitialized()) |
| return; |
| base::win::ScopedComPtr<ITfContext> context; |
| if (FAILED(document_manager_for_editable_->GetTop(context.Receive()))) |
| return; |
| base::win::ScopedComPtr<ITfSource> source; |
| if (FAILED(source.QueryFrom(context))) |
| return; |
| if (source_cookie_ == TF_INVALID_COOKIE) |
| return; |
| if (FAILED(source->UnadviseSink(source_cookie_))) |
| return; |
| |
| DCHECK(text_store_.get()); |
| text_store_.reset(); |
| client_id_ = TF_CLIENTID_NULL; |
| } |
| |
| // TsfBridge override. |
| virtual void OnTextInputTypeChanged(TextInputClient* client) OVERRIDE { |
| DCHECK_EQ(MessageLoop::TYPE_UI, MessageLoop::current()->type()); |
| DCHECK(IsInitialized()); |
| |
| if (client != client_) { |
| // Called from not focusing client. Do nothing. |
| return; |
| } |
| |
| DCHECK(client_); |
| const TextInputType type = client_->GetTextInputType(); |
| switch (type) { |
| case TEXT_INPUT_TYPE_NONE: |
| thread_manager_->SetFocus(document_manager_for_non_editable_); |
| break; |
| case TEXT_INPUT_TYPE_PASSWORD: |
| thread_manager_->SetFocus(document_manager_for_password_); |
| break; |
| default: |
| thread_manager_->SetFocus(document_manager_for_editable_); |
| break; |
| } |
| } |
| |
| // TsfBridge override. |
| virtual bool CancelComposition() OVERRIDE { |
| DCHECK_EQ(MessageLoop::TYPE_UI, MessageLoop::current()->type()); |
| DCHECK(IsInitialized()); |
| // If the current focused document manager is not |
| // |document_manager_for_editable_|, do nothing here. |
| if (!IsFocused(document_manager_for_editable_)) |
| return false; |
| |
| base::win::ScopedComPtr<ITfContext> context; |
| if (FAILED(document_manager_for_editable_->GetTop(context.Receive()))) { |
| VLOG(1) << "Failed to get top context."; |
| return false; |
| } |
| |
| base::win::ScopedComPtr<ITfContextOwnerCompositionServices> owner; |
| if (FAILED(owner.QueryFrom(context))) { |
| VLOG(1) << "Failed to get ITfContextOwnerCompositionService."; |
| return false; |
| } |
| // Cancel all composition. |
| owner->TerminateComposition(NULL); |
| return true; |
| } |
| |
| // TsfBridge override. |
| virtual void SetFocusedClient(HWND focused_window, |
| TextInputClient* client) OVERRIDE { |
| DCHECK_EQ(MessageLoop::TYPE_UI, MessageLoop::current()->type()); |
| DCHECK(client); |
| DCHECK(IsInitialized()); |
| client_ = client; |
| text_store_->get()->SetFocusedTextInputClient(focused_window, client); |
| password_text_store_->get()->SetFocusedTextInputClient(focused_window, |
| client); |
| |
| // Synchronize text input type state. |
| OnTextInputTypeChanged(client); |
| } |
| |
| // TsfBridge override. |
| virtual void RemoveFocusedClient(TextInputClient* client) OVERRIDE { |
| DCHECK_EQ(MessageLoop::TYPE_UI, MessageLoop::current()->type()); |
| DCHECK(IsInitialized()); |
| if (client_ == client) { |
| client_ = NULL; |
| text_store_->get()->SetFocusedTextInputClient(NULL, NULL); |
| password_text_store_->get()->SetFocusedTextInputClient(NULL, NULL); |
| } |
| } |
| |
| private: |
| friend struct DefaultSingletonTraits<TsfBridgeDelegate>; |
| |
| // Initializes |document_manager_for_editable_|. |
| bool InitializeForEnabledDocumentManager() { |
| if (FAILED(thread_manager_->CreateDocumentMgr( |
| document_manager_for_editable_.Receive()))) { |
| VLOG(1) << "Failed to create Document Manager."; |
| return false; |
| } |
| |
| TfEditCookie unused_edit_cookie = TF_INVALID_EDIT_COOKIE; |
| text_store_.reset(new scoped_refptr<TsfTextStore>(new TsfTextStore())); |
| |
| base::win::ScopedComPtr<ITfContext> context; |
| if (FAILED(document_manager_for_editable_->CreateContext(client_id_, |
| 0, |
| static_cast<ITextStoreACP*>( |
| text_store_->get()), |
| context.Receive(), |
| &unused_edit_cookie))) { |
| VLOG(1) << "Failed to create Context."; |
| return false; |
| } |
| |
| if (FAILED(document_manager_for_editable_->Push(context))) { |
| VLOG(1) << "Failed to push context."; |
| return false; |
| } |
| |
| base::win::ScopedComPtr<ITfSource> source; |
| if (FAILED(source.QueryFrom(context))) { |
| VLOG(1) << "Failed to get source."; |
| return false; |
| } |
| |
| if (FAILED(source->AdviseSink(IID_ITfTextEditSink, |
| static_cast<ITfTextEditSink*>( |
| text_store_->get()), |
| &source_cookie_))) { |
| VLOG(1) << "AdviseSink failed."; |
| return false; |
| } |
| |
| if (source_cookie_ == TF_INVALID_COOKIE) { |
| VLOG(1) << "The result of cookie is invalid."; |
| return false; |
| } |
| |
| return true; |
| } |
| |
| // Initializes |document_manager_for_password_|. |
| bool InitializeForPasswordDocumentManager() { |
| if (FAILED(thread_manager_->CreateDocumentMgr( |
| document_manager_for_password_.Receive()))) { |
| VLOG(1) << "Failed to create Document Manager."; |
| return false; |
| } |
| |
| TfEditCookie unused_edit_cookie = TF_INVALID_EDIT_COOKIE; |
| password_text_store_.reset( |
| new scoped_refptr<TsfTextStore>(new TsfTextStore())); |
| |
| base::win::ScopedComPtr<ITfContext> context; |
| if (FAILED(document_manager_for_password_->CreateContext( |
| client_id_, |
| 0, |
| static_cast<ITextStoreACP*>(password_text_store_->get()), |
| context.Receive(), |
| &unused_edit_cookie))) { |
| VLOG(1) << "Failed to create Context."; |
| return false; |
| } |
| |
| base::win::ScopedComPtr<ITfCompartmentMgr> compartment_mgr; |
| if (FAILED(compartment_mgr.QueryFrom(context))) { |
| VLOG(1) << "Failed to get CompartmentMgr."; |
| return false; |
| } |
| |
| base::win::ScopedComPtr<ITfCompartment> disabled_compartment; |
| if (FAILED(compartment_mgr->GetCompartment( |
| GUID_COMPARTMENT_KEYBOARD_DISABLED, |
| disabled_compartment.Receive()))) { |
| VLOG(1) << "Failed to get keyboard disabled compartment."; |
| return false; |
| } |
| |
| base::win::ScopedVariant variant; |
| variant.Set(static_cast<int32>(1)); |
| if (FAILED(disabled_compartment->SetValue(client_id_, &variant))) { |
| VLOG(1) << "Failed to disable the DocumentMgr."; |
| return false; |
| } |
| |
| base::win::ScopedComPtr<ITfCompartment> empty_context; |
| if (FAILED(compartment_mgr->GetCompartment(GUID_COMPARTMENT_EMPTYCONTEXT, |
| empty_context.Receive()))) { |
| VLOG(1) << "Failed to get empty context compartment."; |
| return false; |
| } |
| base::win::ScopedVariant empty_context_variant; |
| empty_context_variant.Set(static_cast<int32>(1)); |
| if (FAILED(empty_context->SetValue(client_id_, &empty_context_variant))) { |
| VLOG(1) << "Failed to set empty context."; |
| return false; |
| } |
| |
| if (FAILED(document_manager_for_password_->Push(context))) { |
| VLOG(1) << "Failed to push context."; |
| return false; |
| } |
| |
| base::win::ScopedComPtr<ITfSource> source; |
| if (FAILED(source.QueryFrom(context))) { |
| VLOG(1) << "Failed to get source."; |
| return false; |
| } |
| |
| if (FAILED(source->AdviseSink(IID_ITfTextEditSink, |
| static_cast<ITfTextEditSink*>( |
| password_text_store_->get()), |
| &source_cookie_))) { |
| VLOG(1) << "AdviseSink failed."; |
| return false; |
| } |
| |
| if (source_cookie_ == TF_INVALID_COOKIE) { |
| VLOG(1) << "The result of cookie is invalid."; |
| return false; |
| } |
| |
| return true; |
| } |
| |
| // Initializes |document_manager_for_non_editable_|. |
| bool InitializeForDisabledDocumentManager() { |
| if (FAILED(thread_manager_->CreateDocumentMgr( |
| document_manager_for_non_editable_.Receive()))) { |
| VLOG(1) << "Failed to create Document Manager."; |
| return false; |
| } |
| |
| base::win::ScopedComPtr<ITfContext> disabled_context; |
| DWORD disabled_edit_cookie; |
| // Passing NULL as ITextStoreACP to disable IMEs completely. |
| if (document_manager_for_non_editable_->CreateContext( |
| client_id_, |
| 0, |
| NULL, |
| disabled_context.Receive(), |
| &disabled_edit_cookie)) { |
| VLOG(1) << "Failed to create disabled Context"; |
| return false; |
| } |
| |
| base::win::ScopedComPtr<ITfCompartmentMgr> compartment_mgr; |
| if (FAILED(compartment_mgr.QueryFrom(disabled_context))) { |
| VLOG(1) << "Failed to get CompartmentMgr."; |
| return false; |
| } |
| |
| base::win::ScopedComPtr<ITfCompartment> disabled_compartment; |
| if (FAILED(compartment_mgr->GetCompartment( |
| GUID_COMPARTMENT_KEYBOARD_DISABLED, |
| disabled_compartment.Receive()))) { |
| VLOG(1) << "Failed to get keyboard disabled compartment."; |
| return false; |
| } |
| |
| base::win::ScopedVariant variant; |
| variant.Set(static_cast<int32>(1)); |
| if (FAILED(disabled_compartment->SetValue(client_id_, &variant))) { |
| VLOG(1) << "Failed to disable the DocumentMgr."; |
| return false; |
| } |
| |
| base::win::ScopedComPtr<ITfCompartment> empty_context; |
| if (FAILED(compartment_mgr->GetCompartment(GUID_COMPARTMENT_EMPTYCONTEXT, |
| empty_context.Receive()))) { |
| VLOG(1) << "Failed to get empty context compartment."; |
| return false; |
| } |
| base::win::ScopedVariant empty_context_variant; |
| empty_context_variant.Set(static_cast<int32>(1)); |
| if (FAILED(empty_context->SetValue(client_id_, &empty_context_variant))) { |
| VLOG(1) << "Failed to set empty context."; |
| return false; |
| } |
| |
| if (FAILED(document_manager_for_non_editable_->Push(disabled_context))) { |
| VLOG(1) << "Failed to push disabled context."; |
| return false; |
| } |
| |
| return true; |
| } |
| |
| // Returns true if |document_manager| is the focused document manager. |
| bool IsFocused(base::win::ScopedComPtr<ITfDocumentMgr> document_manager) { |
| base::win::ScopedComPtr<ITfDocumentMgr> focused_document_mangaer; |
| if (FAILED(thread_manager_->GetFocus(focused_document_mangaer.Receive()))) |
| return false; |
| return focused_document_mangaer.IsSameObject(document_manager); |
| } |
| |
| // Returns true if already initialized. |
| bool IsInitialized() { |
| return client_id_ != TF_CLIENTID_NULL; |
| } |
| |
| // An ITfContext object to be used in normal text field. This document manager |
| // contains a TsfTextStore instance and an activated context. |
| base::win::ScopedComPtr<ITfDocumentMgr> document_manager_for_editable_; |
| |
| // Altough TSF support IME enable/disable switching without context changing, |
| // some IMEs don't change their state. So we should switch a focus to a |
| // |document_manager_for_non_editable_| which contains deactivated context to |
| // disable IMEs. |
| base::win::ScopedComPtr<ITfDocumentMgr> document_manager_for_non_editable_; |
| |
| // In the case of password field, we can't use |
| // |document_manager_for_non_editable_| because password field should accept |
| // key input but IMEs must not be enbaled. In addition to this, a user expects |
| // on-screen keyboard layout should be changed to a sutable one on the |
| // password field. To achieve these requirements, we use a different document |
| // manager where 1) the context has special TSF compartments to specify IME |
| // status, and 2) TSF TextStore has a special input scope to specify password |
| // type keyboard layout. |
| base::win::ScopedComPtr<ITfDocumentMgr> document_manager_for_password_; |
| |
| // An ITfThreadMgr object to be used in focus and document management. |
| base::win::ScopedComPtr<ITfThreadMgr> thread_manager_; |
| |
| // A TextStore object to be used in communicating with IME for normal field. |
| scoped_ptr<scoped_refptr<TsfTextStore> > text_store_; |
| |
| // A TextStore object to be used in communicating with IME for password field. |
| scoped_ptr<scoped_refptr<TsfTextStore> > password_text_store_; |
| |
| DWORD source_cookie_; |
| TfClientId client_id_; |
| |
| // Current focused text input client. Do not free |client_|. |
| TextInputClient* client_; |
| |
| DISALLOW_COPY_AND_ASSIGN(TsfBridgeDelegate); |
| }; |
| |
| // We use thread local storage for TsfBridge lifespan management. |
| base::ThreadLocalStorage::StaticSlot tls_tsf_bridge = TLS_INITIALIZER; |
| |
| // Used for releasing TLS object. |
| void FreeTlsTsfBridgeDelegate(void* data) { |
| TsfBridgeDelegate* delegate = static_cast<TsfBridgeDelegate*>(data); |
| delete delegate; |
| } |
| |
| } // namespace |
| |
| TsfBridge::TsfBridge() { |
| } |
| |
| TsfBridge::~TsfBridge() { |
| } |
| |
| // static |
| bool TsfBridge::Initialize() { |
| if (MessageLoop::current()->type() != MessageLoop::TYPE_UI) { |
| VLOG(1) << "Do not use TsfBridge without UI thread."; |
| return false; |
| } |
| tls_tsf_bridge.Initialize(FreeTlsTsfBridgeDelegate); |
| TsfBridgeDelegate* delegate = new TsfBridgeDelegate(); |
| tls_tsf_bridge.Set(delegate); |
| return delegate->Initialize(); |
| } |
| |
| // static |
| TsfBridge* TsfBridge::ReplaceForTesting(TsfBridge* bridge) { |
| if (MessageLoop::current()->type() != MessageLoop::TYPE_UI) { |
| VLOG(1) << "Do not use TsfBridge without UI thread."; |
| return NULL; |
| } |
| TsfBridge* old_bridge = TsfBridge::GetInstance(); |
| tls_tsf_bridge.Set(bridge); |
| return old_bridge; |
| } |
| |
| // static |
| TsfBridge* TsfBridge::GetInstance() { |
| if (MessageLoop::current()->type() != MessageLoop::TYPE_UI) { |
| VLOG(1) << "Do not use TsfBridge without UI thread."; |
| return NULL; |
| } |
| TsfBridgeDelegate* delegate = |
| static_cast<TsfBridgeDelegate*>(tls_tsf_bridge.Get()); |
| DCHECK(delegate) << "Do no call GetInstance before TsfBridge::Initialize."; |
| return delegate; |
| } |
| |
| } // namespace ui |