// 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 #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 thread_compartment_manager; if (FAILED(thread_compartment_manager.QueryFrom(thread_manager_))) { VLOG(1) << "Failed to get ITfCompartmentMgr."; return false; } base::win::ScopedComPtr 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 context; if (FAILED(document_manager_for_editable_->GetTop(context.Receive()))) return; base::win::ScopedComPtr 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 context; if (FAILED(document_manager_for_editable_->GetTop(context.Receive()))) { VLOG(1) << "Failed to get top context."; return false; } base::win::ScopedComPtr 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; // 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(new TsfTextStore())); base::win::ScopedComPtr context; if (FAILED(document_manager_for_editable_->CreateContext(client_id_, 0, static_cast( 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 source; if (FAILED(source.QueryFrom(context))) { VLOG(1) << "Failed to get source."; return false; } if (FAILED(source->AdviseSink(IID_ITfTextEditSink, static_cast( 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(new TsfTextStore())); base::win::ScopedComPtr context; if (FAILED(document_manager_for_password_->CreateContext( client_id_, 0, static_cast(password_text_store_->get()), context.Receive(), &unused_edit_cookie))) { VLOG(1) << "Failed to create Context."; return false; } base::win::ScopedComPtr compartment_mgr; if (FAILED(compartment_mgr.QueryFrom(context))) { VLOG(1) << "Failed to get CompartmentMgr."; return false; } base::win::ScopedComPtr 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(1)); if (FAILED(disabled_compartment->SetValue(client_id_, &variant))) { VLOG(1) << "Failed to disable the DocumentMgr."; return false; } base::win::ScopedComPtr 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(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 source; if (FAILED(source.QueryFrom(context))) { VLOG(1) << "Failed to get source."; return false; } if (FAILED(source->AdviseSink(IID_ITfTextEditSink, static_cast( 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 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 compartment_mgr; if (FAILED(compartment_mgr.QueryFrom(disabled_context))) { VLOG(1) << "Failed to get CompartmentMgr."; return false; } base::win::ScopedComPtr 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(1)); if (FAILED(disabled_compartment->SetValue(client_id_, &variant))) { VLOG(1) << "Failed to disable the DocumentMgr."; return false; } base::win::ScopedComPtr 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(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 document_manager) { base::win::ScopedComPtr 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 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 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 document_manager_for_password_; // An ITfThreadMgr object to be used in focus and document management. base::win::ScopedComPtr thread_manager_; // A TextStore object to be used in communicating with IME for normal field. scoped_ptr > text_store_; // A TextStore object to be used in communicating with IME for password field. scoped_ptr > 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(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(tls_tsf_bridge.Get()); DCHECK(delegate) << "Do no call GetInstance before TsfBridge::Initialize."; return delegate; } } // namespace ui