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
20namespace metro_driver {
21namespace {
22
23// An implementation of ITfLanguageProfileNotifySink interface, which will be
24// used to receive notifications when the text input source is changed.
25class 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
86class 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
137scoped_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