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:
InputSourceMonitor()29 InputSourceMonitor()
30 : cookie_(TF_INVALID_COOKIE) {
31 }
32
33 BEGIN_COM_MAP(InputSourceMonitor)
COM_INTERFACE_ENTRY(ITfLanguageProfileNotifySink)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
SetCallback(base::Closure on_language_chanaged)51 void SetCallback(base::Closure on_language_chanaged) {
52 on_language_chanaged_ = on_language_chanaged;
53 }
54
Unadvise()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:
STDMETHOD(OnLanguageChange)66 STDMETHOD(OnLanguageChange)(LANGID langid, BOOL *accept) OVERRIDE {
67 if (!accept)
68 return E_INVALIDARG;
69 *accept = TRUE;
70 return S_OK;
71 }
72
STDMETHOD(OnLanguageChanged)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:
InputSourceImpl(ITfInputProcessorProfileMgr * profile_manager,InputSourceMonitor * monitor)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 }
~InputSourceImpl()95 virtual ~InputSourceImpl() {
96 monitor_->SetCallback(base::Closure());
97 monitor_->Unadvise();
98 }
99
100 private:
101 // InputSource overrides.
GetActiveSource(LANGID * langid,bool * is_ime)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 }
AddObserver(InputSourceObserver * observer)115 virtual void AddObserver(InputSourceObserver* observer) OVERRIDE {
116 observer_list_.AddObserver(observer);
117 }
RemoveObserver(InputSourceObserver * observer)118 virtual void RemoveObserver(InputSourceObserver* observer) OVERRIDE {
119 observer_list_.RemoveObserver(observer);
120 }
OnLanguageChanged()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
Create()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