Home | History | Annotate | Download | only in ime
      1 // Copyright 2013 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "win8/metro_driver/ime/text_service.h"
      6 
      7 #include <msctf.h>
      8 
      9 #include "base/logging.h"
     10 #include "base/win/scoped_variant.h"
     11 #include "ui/metro_viewer/ime_types.h"
     12 #include "win8/metro_driver/ime/text_service_delegate.h"
     13 #include "win8/metro_driver/ime/text_store.h"
     14 #include "win8/metro_driver/ime/text_store_delegate.h"
     15 
     16 // Architecture overview of input method support on Ash mode:
     17 //
     18 // Overview:
     19 // On Ash mode, the system keyboard focus is owned by the metro_driver process
     20 // while most of event handling are still implemented in the browser process.
     21 // Thus the metro_driver basically works as a proxy that simply forwards
     22 // keyevents to the metro_driver process. IME support must be involved somewhere
     23 // in this flow.
     24 //
     25 // In short, we need to interact with an IME in the metro_driver process since
     26 // TSF (Text Services Framework) runtime wants to processes keyevents while
     27 // (and only while) the attached UI thread owns keyboard focus.
     28 //
     29 // Due to this limitation, we need to split IME handling into two parts, one
     30 // is in the metro_driver process and the other is in the browser process.
     31 // The metro_driver process is responsible for implementing the primary data
     32 // store for the composition text and wiring it up with an IME via TSF APIs.
     33 // On the other hand, the browser process is responsible for calculating
     34 // character position in the composition text whenever the composition text
     35 // is updated.
     36 //
     37 // IPC overview:
     38 // Fortunately, we don't need so many IPC messages to support IMEs. In fact,
     39 // only 4 messages are required to enable basic IME functionality.
     40 //
     41 //   metro_driver process -> browser process
     42 //     Message Type:
     43 //       - MetroViewerHostMsg_ImeCompositionChanged
     44 //       - MetroViewerHostMsg_ImeTextCommitted
     45 //     Message Routing:
     46 //       TextServiceImpl
     47 //         -> ChromeAppViewAsh
     48 //         -- (process boundary) --
     49 //         -> RemoteWindowTreeHostWin
     50 //         -> RemoteInputMethodWin
     51 //
     52 //   browser process -> metro_driver process
     53 //     Message Type:
     54 //       - MetroViewerHostMsg_ImeCancelComposition
     55 //       - MetroViewerHostMsg_ImeTextInputClientUpdated
     56 //     Message Routing:
     57 //       RemoteInputMethodWin
     58 //         -> RemoteWindowTreeHostWin
     59 //         -- (process boundary) --
     60 //         -> ChromeAppViewAsh
     61 //         -> TextServiceImpl
     62 //
     63 // Note that a keyevent may be forwarded through a different path. When a
     64 // keyevent is not handled by an IME, such keyevent and subsequent character
     65 // events will be sent from the metro_driver process to the browser process as
     66 // following IPC messages.
     67 //  - MetroViewerHostMsg_KeyDown
     68 //  - MetroViewerHostMsg_KeyUp
     69 //  - MetroViewerHostMsg_Character
     70 //
     71 // How TextServiceImpl works:
     72 // Here is the list of the major tasks that are handled in TextServiceImpl.
     73 //  - Manages a session object obtained from TSF runtime. We need them to call
     74 //    most of TSF APIs.
     75 //  - Handles OnDocumentChanged event. Whenever the document type is changed,
     76 //    TextServiceImpl destroyes the current document and initializes new one
     77 //    according to the given |input_scopes|.
     78 //  - Stores the |composition_character_bounds_| passed from OnDocumentChanged
     79 //    event so that an IME or on-screen keyboard can query the character
     80 //    position synchronously.
     81 // The most complicated part is the OnDocumentChanged handler. Since some IMEs
     82 // such as Japanese IMEs drastically change their behavior depending on
     83 // properties exposed from the virtual document, we need to set up a lot
     84 // properties carefully and correctly. See DocumentBinding class in this file
     85 // about what will be involved in this multi-phase construction. See also
     86 // text_store.cc and input_scope.cc for more underlying details.
     87 
     88 namespace metro_driver {
     89 namespace {
     90 
     91 // Japanese IME expects the default value of this compartment is
     92 // TF_SENTENCEMODE_PHRASEPREDICT to emulate IMM32 behavior. This value is
     93 // managed per thread, thus setting this value at once is sufficient. This
     94 // value never affects non-Japanese IMEs.
     95 bool InitializeSentenceMode(ITfThreadMgr2* thread_manager,
     96                             TfClientId client_id) {
     97   base::win::ScopedComPtr<ITfCompartmentMgr> thread_compartment_manager;
     98   HRESULT hr = thread_compartment_manager.QueryFrom(thread_manager);
     99   if (FAILED(hr)) {
    100     LOG(ERROR) << "QueryFrom failed. hr = " << hr;
    101     return false;
    102   }
    103   base::win::ScopedComPtr<ITfCompartment> sentence_compartment;
    104   hr = thread_compartment_manager->GetCompartment(
    105       GUID_COMPARTMENT_KEYBOARD_INPUTMODE_SENTENCE,
    106       sentence_compartment.Receive());
    107   if (FAILED(hr)) {
    108     LOG(ERROR) << "ITfCompartment::GetCompartment failed. hr = " << hr;
    109     return false;
    110   }
    111 
    112   base::win::ScopedVariant sentence_variant;
    113   sentence_variant.Set(TF_SENTENCEMODE_PHRASEPREDICT);
    114   hr = sentence_compartment->SetValue(client_id, &sentence_variant);
    115   if (FAILED(hr)) {
    116     LOG(ERROR) << "ITfCompartment::SetValue failed. hr = " << hr;
    117     return false;
    118   }
    119   return true;
    120 }
    121 
    122 // Initializes |context| as disabled context where IMEs will be disabled.
    123 bool InitializeDisabledContext(ITfContext* context, TfClientId client_id) {
    124   base::win::ScopedComPtr<ITfCompartmentMgr> compartment_mgr;
    125   HRESULT hr = compartment_mgr.QueryFrom(context);
    126   if (FAILED(hr)) {
    127     LOG(ERROR) << "QueryFrom failed. hr = " << hr;
    128     return false;
    129   }
    130 
    131   base::win::ScopedComPtr<ITfCompartment> disabled_compartment;
    132   hr = compartment_mgr->GetCompartment(GUID_COMPARTMENT_KEYBOARD_DISABLED,
    133                                        disabled_compartment.Receive());
    134   if (FAILED(hr)) {
    135     LOG(ERROR) << "ITfCompartment::GetCompartment failed. hr = " << hr;
    136     return false;
    137   }
    138 
    139   base::win::ScopedVariant variant;
    140   variant.Set(1);
    141   hr = disabled_compartment->SetValue(client_id, &variant);
    142   if (FAILED(hr)) {
    143     LOG(ERROR) << "ITfCompartment::SetValue failed. hr = " << hr;
    144     return false;
    145   }
    146 
    147   base::win::ScopedComPtr<ITfCompartment> empty_context;
    148   hr = compartment_mgr->GetCompartment(GUID_COMPARTMENT_EMPTYCONTEXT,
    149                                        empty_context.Receive());
    150   if (FAILED(hr)) {
    151     LOG(ERROR) << "ITfCompartment::GetCompartment failed. hr = " << hr;
    152     return false;
    153   }
    154 
    155   base::win::ScopedVariant empty_context_variant;
    156   empty_context_variant.Set(static_cast<int32>(1));
    157   hr = empty_context->SetValue(client_id, &empty_context_variant);
    158   if (FAILED(hr)) {
    159     LOG(ERROR) << "ITfCompartment::SetValue failed. hr = " << hr;
    160     return false;
    161   }
    162 
    163   return true;
    164 }
    165 
    166 bool IsPasswordField(const std::vector<InputScope>& input_scopes) {
    167   return std::find(input_scopes.begin(), input_scopes.end(), IS_PASSWORD) !=
    168       input_scopes.end();
    169 }
    170 
    171 // A class that manages the lifetime of the event callback registration. When
    172 // this object is destroyed, corresponding event callback will be unregistered.
    173 class EventSink {
    174  public:
    175   EventSink(DWORD cookie, base::win::ScopedComPtr<ITfSource> source)
    176       : cookie_(cookie),
    177         source_(source) {}
    178   ~EventSink() {
    179     if (!source_ || cookie_ != TF_INVALID_COOKIE)
    180       return;
    181     source_->UnadviseSink(cookie_);
    182     cookie_ = TF_INVALID_COOKIE;
    183     source_.Release();
    184   }
    185 
    186  private:
    187   DWORD cookie_;
    188   base::win::ScopedComPtr<ITfSource> source_;
    189   DISALLOW_COPY_AND_ASSIGN(EventSink);
    190 };
    191 
    192 scoped_ptr<EventSink> CreateTextEditSink(ITfContext* context,
    193                                          ITfTextEditSink* text_store) {
    194   DCHECK(text_store);
    195   base::win::ScopedComPtr<ITfSource> source;
    196   DWORD cookie = TF_INVALID_EDIT_COOKIE;
    197   HRESULT hr = source.QueryFrom(context);
    198   if (FAILED(hr)) {
    199     LOG(ERROR) << "QueryFrom failed, hr = " << hr;
    200     return scoped_ptr<EventSink>();
    201   }
    202   hr = source->AdviseSink(IID_ITfTextEditSink, text_store, &cookie);
    203   if (FAILED(hr)) {
    204     LOG(ERROR) << "AdviseSink failed, hr = " << hr;
    205     return scoped_ptr<EventSink>();
    206   }
    207   return scoped_ptr<EventSink>(new EventSink(cookie, source));
    208 }
    209 
    210 // A set of objects that should have the same lifetime. Following things
    211 // are maintained.
    212 //  - TextStore: a COM object that abstracts text buffer. This object is
    213 //      actually implemented by us in text_store.cc
    214 //  - ITfDocumentMgr: a focusable unit in TSF. This object is implemented by
    215 //      TSF runtime and works as a container of TextStore.
    216 //  - EventSink: an object that ensures that the event callback between
    217 //      TSF runtime and TextStore is unregistered when this object is destroyed.
    218 class DocumentBinding {
    219  public:
    220   ~DocumentBinding() {
    221     if (!document_manager_)
    222       return;
    223     document_manager_->Pop(TF_POPF_ALL);
    224   }
    225 
    226   static scoped_ptr<DocumentBinding> Create(
    227       ITfThreadMgr2* thread_manager,
    228       TfClientId client_id,
    229       const std::vector<InputScope>& input_scopes,
    230       HWND window_handle,
    231       TextStoreDelegate* delegate) {
    232     base::win::ScopedComPtr<ITfDocumentMgr> document_manager;
    233     HRESULT hr = thread_manager->CreateDocumentMgr(document_manager.Receive());
    234     if (FAILED(hr)) {
    235       LOG(ERROR) << "ITfThreadMgr2::CreateDocumentMgr failed. hr = " << hr;
    236       return scoped_ptr<DocumentBinding>();
    237     }
    238 
    239     // Note: In our IPC protocol, an empty |input_scopes| is used to indicate
    240     // that an IME must be disabled in this context. In such case, we need not
    241     // instantiate TextStore.
    242     const bool use_null_text_store = input_scopes.empty();
    243 
    244     scoped_refptr<TextStore> text_store;
    245     if (!use_null_text_store) {
    246       text_store = TextStore::Create(window_handle, input_scopes, delegate);
    247       if (!text_store) {
    248         LOG(ERROR) << "Failed to create TextStore.";
    249         return scoped_ptr<DocumentBinding>();
    250       }
    251     }
    252 
    253     base::win::ScopedComPtr<ITfContext> context;
    254     DWORD edit_cookie = TF_INVALID_EDIT_COOKIE;
    255     hr = document_manager->CreateContext(
    256         client_id,
    257         0,
    258         static_cast<ITextStoreACP*>(text_store.get()),
    259         context.Receive(),
    260         &edit_cookie);
    261     if (FAILED(hr)) {
    262       LOG(ERROR) << "ITfDocumentMgr::CreateContext failed. hr = " << hr;
    263       return scoped_ptr<DocumentBinding>();
    264     }
    265 
    266     // If null-TextStore is used or |input_scopes| looks like a password field,
    267     // set special properties to tell IMEs to be disabled.
    268     if ((use_null_text_store || IsPasswordField(input_scopes)) &&
    269         !InitializeDisabledContext(context, client_id)) {
    270       LOG(ERROR) << "InitializeDisabledContext failed.";
    271       return scoped_ptr<DocumentBinding>();
    272     }
    273 
    274     scoped_ptr<EventSink> text_edit_sink;
    275     if (!use_null_text_store) {
    276       text_edit_sink = CreateTextEditSink(context, text_store);
    277       if (!text_edit_sink) {
    278         LOG(ERROR) << "CreateTextEditSink failed.";
    279         return scoped_ptr<DocumentBinding>();
    280       }
    281     }
    282     hr = document_manager->Push(context);
    283     if (FAILED(hr)) {
    284       LOG(ERROR) << "ITfDocumentMgr::Push failed. hr = " << hr;
    285       return scoped_ptr<DocumentBinding>();
    286     }
    287     return scoped_ptr<DocumentBinding>(
    288         new DocumentBinding(text_store,
    289                             document_manager,
    290                             text_edit_sink.Pass()));
    291   }
    292 
    293   ITfDocumentMgr* document_manager() const {
    294     return document_manager_;
    295   }
    296 
    297   scoped_refptr<TextStore> text_store() const {
    298     return text_store_;
    299   }
    300 
    301  private:
    302   DocumentBinding(scoped_refptr<TextStore> text_store,
    303                   base::win::ScopedComPtr<ITfDocumentMgr> document_manager,
    304                   scoped_ptr<EventSink> text_edit_sink)
    305       : text_store_(text_store),
    306         document_manager_(document_manager),
    307         text_edit_sink_(text_edit_sink.Pass()) {}
    308 
    309   scoped_refptr<TextStore> text_store_;
    310   base::win::ScopedComPtr<ITfDocumentMgr> document_manager_;
    311   scoped_ptr<EventSink> text_edit_sink_;
    312 
    313   DISALLOW_COPY_AND_ASSIGN(DocumentBinding);
    314 };
    315 
    316 class TextServiceImpl : public TextService,
    317                         public TextStoreDelegate {
    318  public:
    319   TextServiceImpl(ITfThreadMgr2* thread_manager,
    320                   TfClientId client_id,
    321                   HWND window_handle,
    322                   TextServiceDelegate* delegate)
    323       : client_id_(client_id),
    324         window_handle_(window_handle),
    325         delegate_(delegate),
    326         thread_manager_(thread_manager) {
    327     DCHECK_NE(TF_CLIENTID_NULL, client_id);
    328     DCHECK(window_handle != NULL);
    329     DCHECK(thread_manager_);
    330   }
    331   virtual ~TextServiceImpl() {
    332     thread_manager_->Deactivate();
    333   }
    334 
    335  private:
    336   // TextService overrides:
    337   virtual void CancelComposition() OVERRIDE {
    338     if (!current_document_) {
    339       VLOG(0) << "|current_document_| is NULL due to the previous error.";
    340       return;
    341     }
    342     TextStore* text_store = current_document_->text_store();
    343     if (!text_store)
    344       return;
    345     text_store->CancelComposition();
    346   }
    347 
    348   virtual void OnDocumentChanged(
    349       const std::vector<int32>& input_scopes,
    350       const std::vector<metro_viewer::CharacterBounds>& character_bounds)
    351       OVERRIDE {
    352     bool document_type_changed = input_scopes_ != input_scopes;
    353     input_scopes_ = input_scopes;
    354     composition_character_bounds_ = character_bounds;
    355     if (document_type_changed)
    356       OnDocumentTypeChanged(input_scopes);
    357   }
    358 
    359   virtual void OnWindowActivated() OVERRIDE {
    360     if (!current_document_) {
    361       VLOG(0) << "|current_document_| is NULL due to the previous error.";
    362       return;
    363     }
    364     ITfDocumentMgr* document_manager = current_document_->document_manager();
    365     if (!document_manager) {
    366       VLOG(0) << "|document_manager| is NULL due to the previous error.";
    367       return;
    368     }
    369     HRESULT hr = thread_manager_->SetFocus(document_manager);
    370     if (FAILED(hr)) {
    371       LOG(ERROR) << "ITfThreadMgr2::SetFocus failed. hr = " << hr;
    372       return;
    373     }
    374   }
    375 
    376   virtual void OnCompositionChanged(
    377       const base::string16& text,
    378       int32 selection_start,
    379       int32 selection_end,
    380       const std::vector<metro_viewer::UnderlineInfo>& underlines) OVERRIDE {
    381     if (!delegate_)
    382       return;
    383     delegate_->OnCompositionChanged(text,
    384                                     selection_start,
    385                                     selection_end,
    386                                     underlines);
    387   }
    388 
    389   virtual void OnTextCommitted(const base::string16& text) OVERRIDE {
    390     if (!delegate_)
    391       return;
    392     delegate_->OnTextCommitted(text);
    393   }
    394 
    395   virtual RECT GetCaretBounds() {
    396     if (composition_character_bounds_.empty()) {
    397       const RECT rect = {};
    398       return rect;
    399     }
    400     const metro_viewer::CharacterBounds& bounds =
    401         composition_character_bounds_[0];
    402     POINT left_top = { bounds.left, bounds.top };
    403     POINT right_bottom = { bounds.right, bounds.bottom };
    404     ClientToScreen(window_handle_, &left_top);
    405     ClientToScreen(window_handle_, &right_bottom);
    406     const RECT rect = {
    407       left_top.x,
    408       left_top.y,
    409       right_bottom.x,
    410       right_bottom.y,
    411     };
    412     return rect;
    413   }
    414 
    415   virtual bool GetCompositionCharacterBounds(uint32 index,
    416                                              RECT* rect) OVERRIDE {
    417     if (index >= composition_character_bounds_.size()) {
    418       return false;
    419     }
    420     const metro_viewer::CharacterBounds& bounds =
    421         composition_character_bounds_[index];
    422     POINT left_top = { bounds.left, bounds.top };
    423     POINT right_bottom = { bounds.right, bounds.bottom };
    424     ClientToScreen(window_handle_, &left_top);
    425     ClientToScreen(window_handle_, &right_bottom);
    426     SetRect(rect, left_top.x, left_top.y, right_bottom.x, right_bottom.y);
    427     return true;
    428   }
    429 
    430   void OnDocumentTypeChanged(const std::vector<int32>& input_scopes) {
    431     std::vector<InputScope> native_input_scopes(input_scopes.size());
    432     for (size_t i = 0; i < input_scopes.size(); ++i)
    433       native_input_scopes[i] = static_cast<InputScope>(input_scopes[i]);
    434     scoped_ptr<DocumentBinding> new_document =
    435         DocumentBinding::Create(thread_manager_.get(),
    436                                 client_id_,
    437                                 native_input_scopes,
    438                                 window_handle_,
    439                                 this);
    440     LOG_IF(ERROR, !new_document) << "Failed to create a new document.";
    441     current_document_.swap(new_document);
    442     OnWindowActivated();
    443   }
    444 
    445   TfClientId client_id_;
    446   HWND window_handle_;
    447   TextServiceDelegate* delegate_;
    448   scoped_ptr<DocumentBinding> current_document_;
    449   base::win::ScopedComPtr<ITfThreadMgr2> thread_manager_;
    450 
    451   // A vector of InputScope enumeration, which represents the document type of
    452   // the focused text field. Note that in our IPC message protocol, an empty
    453   // |input_scopes_| has special meaning that IMEs must be disabled on this
    454   // document.
    455   std::vector<int32> input_scopes_;
    456   // Character bounds of the composition. When there is no composition but this
    457   // vector is not empty, the first element contains the caret bounds.
    458   std::vector<metro_viewer::CharacterBounds> composition_character_bounds_;
    459 
    460   DISALLOW_COPY_AND_ASSIGN(TextServiceImpl);
    461 };
    462 
    463 }  // namespace
    464 
    465 scoped_ptr<TextService>
    466 CreateTextService(TextServiceDelegate* delegate, HWND window_handle) {
    467   if (!delegate)
    468     return scoped_ptr<TextService>();
    469   base::win::ScopedComPtr<ITfThreadMgr2> thread_manager;
    470   HRESULT hr = thread_manager.CreateInstance(CLSID_TF_ThreadMgr);
    471   if (FAILED(hr)) {
    472     LOG(ERROR) << "Failed to create instance of CLSID_TF_ThreadMgr. hr = "
    473                << hr;
    474     return scoped_ptr<TextService>();
    475   }
    476   TfClientId client_id = TF_CLIENTID_NULL;
    477   hr = thread_manager->ActivateEx(&client_id, 0);
    478   if (FAILED(hr)) {
    479     LOG(ERROR) << "ITfThreadMgr2::ActivateEx failed. hr = " << hr;
    480     return scoped_ptr<TextService>();
    481   }
    482   if (!InitializeSentenceMode(thread_manager, client_id)) {
    483     LOG(ERROR) << "InitializeSentenceMode failed.";
    484     thread_manager->Deactivate();
    485     return scoped_ptr<TextService>();
    486   }
    487   return scoped_ptr<TextService>(new TextServiceImpl(thread_manager,
    488                                                      client_id,
    489                                                      window_handle,
    490                                                      delegate));
    491 }
    492 
    493 }  // namespace metro_driver
    494