Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(435)

Side by Side Diff: win8/metro_driver/ime/text_service.cc

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

Powered by Google App Engine
This is Rietveld 408576698