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/input_source.h"
      6 
      7 #include <atlbase.h>
      8 #include <atlcom.h>
      9 #include <msctf.h>
     10 
     11 #include "base/bind.h"
     12 #include "base/callback.h"
     13 #include "base/logging.h"
     14 #include "base/memory/ref_counted.h"
     15 #include "base/observer_list.h"
     16 #include "base/win/scoped_comptr.h"
     17 #include "ui/base/win/atl_module.h"
     18 #include "win8/metro_driver/ime/input_source_observer.h"
     19 
     20 namespace metro_driver {
     21 namespace {
     22 
     23 // An implementation of ITfLanguageProfileNotifySink interface, which will be
     24 // used to receive notifications when the text input source is changed.
     25 class ATL_NO_VTABLE InputSourceMonitor
     26     : public CComObjectRootEx<CComMultiThreadModel>,
     27       public ITfLanguageProfileNotifySink {
     28  public:
     29   InputSourceMonitor()
     30       : cookie_(TF_INVALID_COOKIE) {
     31   }
     32 
     33   BEGIN_COM_MAP(InputSourceMonitor)
     34     COM_INTERFACE_ENTRY(ITfLanguageProfileNotifySink)
     35   END_COM_MAP()
     36 
     37   bool Initialize(ITfSource* source) {
     38     DWORD cookie = TF_INVALID_COOKIE;
     39     HRESULT hr = source->AdviseSink(IID_ITfLanguageProfileNotifySink,
     40                                     this,
     41                                     &cookie);
     42     if (FAILED(hr)) {
     43       LOG(ERROR) << "ITfSource::AdviseSink failed. hr = " << hr;
     44       return false;
     45     }
     46     cookie_ = cookie;
     47     source_ = source;
     48     return true;
     49   }
     50 
     51   void SetCallback(base::Closure on_language_chanaged) {
     52     on_language_chanaged_ = on_language_chanaged;
     53   }
     54 
     55   void Unadvise() {
     56     if (cookie_ == TF_INVALID_COOKIE || !source_)
     57       return;
     58     if (FAILED(source_->UnadviseSink(cookie_)))
     59       return;
     60     cookie_ = TF_INVALID_COOKIE;
     61     source_.Release();
     62   }
     63 
     64  private:
     65   // ITfLanguageProfileNotifySink overrides:
     66   STDMETHOD(OnLanguageChange)(LANGID langid, BOOL *accept) OVERRIDE {
     67     if (!accept)
     68       return E_INVALIDARG;
     69     *accept = TRUE;
     70     return S_OK;
     71   }
     72 
     73   STDMETHOD(OnLanguageChanged)() OVERRIDE {
     74     if (!on_language_chanaged_.is_null())
     75       on_language_chanaged_.Run();
     76     return S_OK;
     77   }
     78 
     79   base::Closure on_language_chanaged_;
     80   base::win::ScopedComPtr<ITfSource> source_;
     81   DWORD cookie_;
     82 
     83   DISALLOW_COPY_AND_ASSIGN(InputSourceMonitor);
     84 };
     85 
     86 class InputSourceImpl : public InputSource {
     87  public:
     88   InputSourceImpl(ITfInputProcessorProfileMgr* profile_manager,
     89                   InputSourceMonitor* monitor)
     90       : profile_manager_(profile_manager),
     91         monitor_(monitor) {
     92     monitor_->SetCallback(base::Bind(&InputSourceImpl::OnLanguageChanged,
     93                                      base::Unretained(this)));
     94   }
     95   virtual ~InputSourceImpl() {
     96     monitor_->SetCallback(base::Closure());
     97     monitor_->Unadvise();
     98   }
     99 
    100  private:
    101   // InputSource overrides.
    102   virtual bool GetActiveSource(LANGID* langid, bool* is_ime) OVERRIDE {
    103     TF_INPUTPROCESSORPROFILE profile = {};
    104     HRESULT hr = profile_manager_->GetActiveProfile(GUID_TFCAT_TIP_KEYBOARD,
    105                                                     &profile);
    106     if (FAILED(hr)) {
    107       LOG(ERROR) << "ITfInputProcessorProfileMgr::GetActiveProfile failed."
    108                  << " hr = " << hr;
    109       return false;
    110     }
    111     *langid = profile.langid;
    112     *is_ime = profile.dwProfileType == TF_PROFILETYPE_INPUTPROCESSOR;
    113     return true;
    114   }
    115   virtual void AddObserver(InputSourceObserver* observer) OVERRIDE {
    116     observer_list_.AddObserver(observer);
    117   }
    118   virtual void RemoveObserver(InputSourceObserver* observer) OVERRIDE {
    119     observer_list_.RemoveObserver(observer);
    120   }
    121   void OnLanguageChanged() {
    122     FOR_EACH_OBSERVER(InputSourceObserver,
    123                       observer_list_,
    124                       OnInputSourceChanged());
    125   }
    126 
    127   base::win::ScopedComPtr<ITfInputProcessorProfileMgr> profile_manager_;
    128   scoped_refptr<InputSourceMonitor> monitor_;
    129   ObserverList<InputSourceObserver> observer_list_;
    130 
    131   DISALLOW_COPY_AND_ASSIGN(InputSourceImpl);
    132 };
    133 
    134 }  // namespace
    135 
    136 // static
    137 scoped_ptr<InputSource> InputSource::Create() {
    138   ui::win::CreateATLModuleIfNeeded();
    139 
    140   base::win::ScopedComPtr<ITfInputProcessorProfileMgr> profile_manager;
    141   HRESULT hr = profile_manager.CreateInstance(CLSID_TF_InputProcessorProfiles);
    142   if (FAILED(hr)) {
    143     LOG(ERROR) << "Failed to instantiate CLSID_TF_InputProcessorProfiles."
    144                << " hr = " << hr;
    145     return scoped_ptr<InputSource>();
    146   }
    147   base::win::ScopedComPtr<ITfSource> profiles_source;
    148   hr = profiles_source.QueryFrom(profile_manager);
    149   if (FAILED(hr)) {
    150     LOG(ERROR) << "QueryFrom to ITfSource failed. hr = " << hr;
    151     return scoped_ptr<InputSource>();
    152   }
    153 
    154   CComObject<InputSourceMonitor>* monitor = NULL;
    155   hr = CComObject<InputSourceMonitor>::CreateInstance(&monitor);
    156   if (FAILED(hr)) {
    157     LOG(ERROR) << "CComObject<InputSourceMonitor>::CreateInstance failed."
    158                << " hr = " << hr;
    159     return scoped_ptr<InputSource>();
    160   }
    161   if (!monitor->Initialize(profiles_source)) {
    162     LOG(ERROR) << "Failed to initialize the monitor.";
    163     return scoped_ptr<InputSource>();
    164   }
    165 
    166   // Transfer the ownership.
    167   return scoped_ptr<InputSource>(new InputSourceImpl(profile_manager, monitor));
    168 }
    169 
    170 }  // namespace metro_driver
    171