OLD | NEW |
| (Empty) |
1 // Copyright (c) 2011 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 "views/ime/input_method_ibus.h" | |
6 | |
7 #include <ibus.h> | |
8 #if defined(TOUCH_UI) | |
9 #include <X11/Xlib.h> | |
10 #include <X11/Xutil.h> | |
11 #endif | |
12 | |
13 #include <algorithm> | |
14 #include <cstring> | |
15 #include <set> | |
16 #include <vector> | |
17 | |
18 #include "base/command_line.h" | |
19 #include "base/basictypes.h" | |
20 #include "base/i18n/char_iterator.h" | |
21 #include "base/logging.h" | |
22 #include "base/string_util.h" | |
23 #include "base/third_party/icu/icu_utf.h" | |
24 #include "base/utf_string_conversions.h" | |
25 #include "ui/base/keycodes/keyboard_codes.h" | |
26 #include "ui/gfx/point.h" | |
27 #include "ui/gfx/rect.h" | |
28 #include "views/events/event.h" | |
29 #include "views/widget/widget.h" | |
30 | |
31 #if defined(USE_AURA) || defined(TOUCH_UI) | |
32 #include "ui/base/keycodes/keyboard_code_conversion_x.h" | |
33 #elif defined(TOOLKIT_USES_GTK) | |
34 #include "ui/base/keycodes/keyboard_code_conversion_gtk.h" | |
35 #endif | |
36 | |
37 namespace { | |
38 | |
39 // A global flag to switch the InputMethod implementation to InputMethodIBus | |
40 bool inputmethod_ibus_enabled = false; | |
41 | |
42 // Converts ibus key state flags to event flags. | |
43 int EventFlagsFromIBusState(guint32 state) { | |
44 return (state & IBUS_LOCK_MASK ? ui::EF_CAPS_LOCK_DOWN : 0) | | |
45 (state & IBUS_CONTROL_MASK ? ui::EF_CONTROL_DOWN : 0) | | |
46 (state & IBUS_SHIFT_MASK ? ui::EF_SHIFT_DOWN : 0) | | |
47 (state & IBUS_MOD1_MASK ? ui::EF_ALT_DOWN : 0) | | |
48 (state & IBUS_BUTTON1_MASK ? ui::EF_LEFT_BUTTON_DOWN : 0) | | |
49 (state & IBUS_BUTTON2_MASK ? ui::EF_MIDDLE_BUTTON_DOWN : 0) | | |
50 (state & IBUS_BUTTON3_MASK ? ui::EF_RIGHT_BUTTON_DOWN : 0); | |
51 } | |
52 | |
53 // Converts event flags to ibus key state flags. | |
54 guint32 IBusStateFromEventFlags(int flags) { | |
55 return (flags & ui::EF_CAPS_LOCK_DOWN ? IBUS_LOCK_MASK : 0) | | |
56 (flags & ui::EF_CONTROL_DOWN ? IBUS_CONTROL_MASK : 0) | | |
57 (flags & ui::EF_SHIFT_DOWN ? IBUS_SHIFT_MASK : 0) | | |
58 (flags & ui::EF_ALT_DOWN ? IBUS_MOD1_MASK : 0) | | |
59 (flags & ui::EF_LEFT_BUTTON_DOWN ? IBUS_BUTTON1_MASK : 0) | | |
60 (flags & ui::EF_MIDDLE_BUTTON_DOWN ? IBUS_BUTTON2_MASK : 0) | | |
61 (flags & ui::EF_RIGHT_BUTTON_DOWN ? IBUS_BUTTON3_MASK : 0); | |
62 } | |
63 | |
64 void IBusKeyEventFromViewsKeyEvent(const views::KeyEvent& key, | |
65 guint32* ibus_keyval, | |
66 guint32* ibus_keycode, | |
67 guint32* ibus_state) { | |
68 #if defined(USE_AURA) | |
69 // TODO(yusukes): Handle native_event()? | |
70 *ibus_keyval = ui::XKeysymForWindowsKeyCode( | |
71 key.key_code(), key.IsShiftDown() ^ key.IsCapsLockDown()); | |
72 *ibus_keycode = 0; | |
73 #elif defined(TOUCH_UI) | |
74 if (key.native_event()) { | |
75 XKeyEvent* x_key = reinterpret_cast<XKeyEvent*>(key.native_event()); | |
76 // Yes, ibus uses X11 keysym. We cannot use XLookupKeysym(), which doesn't | |
77 // translate Shift and CapsLock states. | |
78 KeySym keysym = NoSymbol; | |
79 ::XLookupString(x_key, NULL, 0, &keysym, NULL); | |
80 *ibus_keyval = keysym; | |
81 *ibus_keycode = x_key->keycode; | |
82 } else { | |
83 *ibus_keyval = ui::XKeysymForWindowsKeyCode( | |
84 key.key_code(), key.IsShiftDown() ^ key.IsCapsLockDown()); | |
85 *ibus_keycode = 0; | |
86 } | |
87 #elif defined(TOOLKIT_USES_GTK) | |
88 if (key.gdk_event()) { | |
89 GdkEventKey* gdk_key = reinterpret_cast<GdkEventKey*>(key.gdk_event()); | |
90 *ibus_keyval = gdk_key->keyval; | |
91 *ibus_keycode = gdk_key->hardware_keycode; | |
92 } else { | |
93 *ibus_keyval = ui::GdkKeyCodeForWindowsKeyCode( | |
94 key.key_code(), key.IsShiftDown() ^ key.IsCapsLockDown()); | |
95 *ibus_keycode = 0; | |
96 } | |
97 #endif | |
98 | |
99 *ibus_state = IBusStateFromEventFlags(key.flags()); | |
100 if (key.type() == ui::ET_KEY_RELEASED) | |
101 *ibus_state |= IBUS_RELEASE_MASK; | |
102 } | |
103 | |
104 void ExtractCompositionTextFromIBusPreedit(IBusText* text, | |
105 guint cursor_position, | |
106 ui::CompositionText* composition) { | |
107 composition->Clear(); | |
108 composition->text = UTF8ToUTF16(text->text); | |
109 | |
110 if (composition->text.empty()) | |
111 return; | |
112 | |
113 // ibus uses character index for cursor position and attribute range, but we | |
114 // use char16 offset for them. So we need to do conversion here. | |
115 std::vector<size_t> char16_offsets; | |
116 size_t length = composition->text.length(); | |
117 base::i18n::UTF16CharIterator char_iterator(&composition->text); | |
118 do { | |
119 char16_offsets.push_back(char_iterator.array_pos()); | |
120 } while (char_iterator.Advance()); | |
121 | |
122 // The text length in Unicode characters. | |
123 guint char_length = static_cast<guint>(char16_offsets.size()); | |
124 // Make sure we can convert the value of |char_length| as well. | |
125 char16_offsets.push_back(length); | |
126 | |
127 size_t cursor_offset = | |
128 char16_offsets[std::min(char_length, cursor_position)]; | |
129 | |
130 composition->selection = ui::Range(cursor_offset); | |
131 if (text->attrs) { | |
132 guint i = 0; | |
133 while (true) { | |
134 IBusAttribute* attr = ibus_attr_list_get(text->attrs, i++); | |
135 if (!attr) | |
136 break; | |
137 if (attr->type != IBUS_ATTR_TYPE_UNDERLINE && | |
138 attr->type != IBUS_ATTR_TYPE_BACKGROUND) { | |
139 continue; | |
140 } | |
141 guint start = std::min(char_length, attr->start_index); | |
142 guint end = std::min(char_length, attr->end_index); | |
143 if (start >= end) | |
144 continue; | |
145 ui::CompositionUnderline underline( | |
146 char16_offsets[start], char16_offsets[end], | |
147 SK_ColorBLACK, false /* thick */); | |
148 if (attr->type == IBUS_ATTR_TYPE_BACKGROUND) { | |
149 underline.thick = true; | |
150 // If the cursor is at start or end of this underline, then we treat | |
151 // it as the selection range as well, but make sure to set the cursor | |
152 // position to the selection end. | |
153 if (underline.start_offset == cursor_offset) { | |
154 composition->selection.set_start(underline.end_offset); | |
155 composition->selection.set_end(cursor_offset); | |
156 } else if (underline.end_offset == cursor_offset) { | |
157 composition->selection.set_start(underline.start_offset); | |
158 composition->selection.set_end(cursor_offset); | |
159 } | |
160 } else if (attr->type == IBUS_ATTR_TYPE_UNDERLINE) { | |
161 if (attr->value == IBUS_ATTR_UNDERLINE_DOUBLE) | |
162 underline.thick = true; | |
163 else if (attr->value == IBUS_ATTR_UNDERLINE_ERROR) | |
164 underline.color = SK_ColorRED; | |
165 } | |
166 composition->underlines.push_back(underline); | |
167 } | |
168 } | |
169 | |
170 // Use a black thin underline by default. | |
171 if (composition->underlines.empty()) { | |
172 composition->underlines.push_back(ui::CompositionUnderline( | |
173 0, length, SK_ColorBLACK, false /* thick */)); | |
174 } | |
175 } | |
176 | |
177 // A switch to enable InputMethodIBus | |
178 const char kEnableInputMethodIBusSwitch[] = "enable-inputmethod-ibus"; | |
179 | |
180 } // namespace | |
181 | |
182 namespace views { | |
183 | |
184 // InputMethodIBus::PendingKeyEvent implementation ---------------------------- | |
185 class InputMethodIBus::PendingKeyEvent { | |
186 public: | |
187 PendingKeyEvent(InputMethodIBus* input_method, const KeyEvent& key, | |
188 guint32 ibus_keyval); | |
189 ~PendingKeyEvent(); | |
190 | |
191 // Abandon this pending key event. Its result will just be discarded. | |
192 void abandon() { input_method_ = NULL; } | |
193 | |
194 InputMethodIBus* input_method() const { return input_method_; } | |
195 | |
196 // Process this pending key event after we receive its result from the input | |
197 // method. It just call through InputMethodIBus::ProcessKeyEventPostIME(). | |
198 void ProcessPostIME(bool handled); | |
199 | |
200 private: | |
201 InputMethodIBus* input_method_; | |
202 | |
203 // Complete information of a views::KeyEvent. Sadly, views::KeyEvent doesn't | |
204 // support copy. | |
205 ui::EventType type_; | |
206 int flags_; | |
207 ui::KeyboardCode key_code_; | |
208 uint16 character_; | |
209 uint16 unmodified_character_; | |
210 | |
211 guint32 ibus_keyval_; | |
212 | |
213 #if defined(TOUCH_UI) | |
214 // corresponding XEvent data of a views::KeyEvent. It's a plain struct so we | |
215 // can do bitwise copy. | |
216 XKeyEvent x_event_; | |
217 #endif | |
218 | |
219 DISALLOW_COPY_AND_ASSIGN(PendingKeyEvent); | |
220 }; | |
221 | |
222 InputMethodIBus::PendingKeyEvent::PendingKeyEvent(InputMethodIBus* input_method, | |
223 const KeyEvent& key, | |
224 guint32 ibus_keyval) | |
225 : input_method_(input_method), | |
226 type_(key.type()), | |
227 flags_(key.flags()), | |
228 key_code_(key.key_code()), | |
229 character_(key.GetCharacter()), | |
230 unmodified_character_(key.GetUnmodifiedCharacter()), | |
231 ibus_keyval_(ibus_keyval) { | |
232 DCHECK(input_method_); | |
233 | |
234 #if defined(TOUCH_UI) | |
235 if (key.native_event()) | |
236 x_event_ = *reinterpret_cast<XKeyEvent*>(key.native_event()); | |
237 else | |
238 memset(&x_event_, 0, sizeof(x_event_)); | |
239 #endif | |
240 } | |
241 | |
242 InputMethodIBus::PendingKeyEvent::~PendingKeyEvent() { | |
243 if (input_method_) | |
244 input_method_->FinishPendingKeyEvent(this); | |
245 } | |
246 | |
247 void InputMethodIBus::PendingKeyEvent::ProcessPostIME(bool handled) { | |
248 if (!input_method_) | |
249 return; | |
250 | |
251 #if defined(TOUCH_UI) | |
252 if (x_event_.type == KeyPress || x_event_.type == KeyRelease) { | |
253 KeyEvent key(reinterpret_cast<XEvent*>(&x_event_)); | |
254 input_method_->ProcessKeyEventPostIME(key, ibus_keyval_, handled); | |
255 return; | |
256 } | |
257 #endif | |
258 KeyEvent key(type_, key_code_, flags_); | |
259 if (key_code_ == ui::VKEY_UNKNOWN) { | |
260 key.set_character(character_); | |
261 key.set_unmodified_character(unmodified_character_); | |
262 } | |
263 input_method_->ProcessKeyEventPostIME(key, ibus_keyval_, handled); | |
264 } | |
265 | |
266 // InputMethodIBus::PendingCreateICRequest implementation --------------------- | |
267 class InputMethodIBus::PendingCreateICRequest { | |
268 public: | |
269 PendingCreateICRequest(InputMethodIBus* input_method, | |
270 PendingCreateICRequest** request_ptr); | |
271 ~PendingCreateICRequest(); | |
272 | |
273 // Abandon this pending key event. Its result will just be discarded. | |
274 void abandon() { | |
275 input_method_ = NULL; | |
276 request_ptr_ = NULL; | |
277 } | |
278 | |
279 // Stores the result input context to |input_method_|, or abandon it if | |
280 // |input_method_| is NULL. | |
281 void StoreOrAbandonInputContext(IBusInputContext* ic); | |
282 | |
283 private: | |
284 InputMethodIBus* input_method_; | |
285 PendingCreateICRequest** request_ptr_; | |
286 }; | |
287 | |
288 InputMethodIBus::PendingCreateICRequest::PendingCreateICRequest( | |
289 InputMethodIBus* input_method, | |
290 PendingCreateICRequest** request_ptr) | |
291 : input_method_(input_method), | |
292 request_ptr_(request_ptr) { | |
293 } | |
294 | |
295 InputMethodIBus::PendingCreateICRequest::~PendingCreateICRequest() { | |
296 if (request_ptr_) { | |
297 DCHECK_EQ(*request_ptr_, this); | |
298 *request_ptr_ = NULL; | |
299 } | |
300 } | |
301 | |
302 void InputMethodIBus::PendingCreateICRequest::StoreOrAbandonInputContext( | |
303 IBusInputContext* ic) { | |
304 if (input_method_) { | |
305 input_method_->SetContext(ic); | |
306 } else { | |
307 // ibus_proxy_destroy() will not really release the object, we still need | |
308 // to call g_object_unref() explicitly. | |
309 ibus_proxy_destroy(reinterpret_cast<IBusProxy *>(ic)); | |
310 g_object_unref(ic); | |
311 } | |
312 } | |
313 | |
314 // InputMethodIBus implementation --------------------------------------------- | |
315 InputMethodIBus::InputMethodIBus(internal::InputMethodDelegate* delegate) | |
316 : context_(NULL), | |
317 pending_create_ic_request_(NULL), | |
318 context_focused_(false), | |
319 composing_text_(false), | |
320 composition_changed_(false), | |
321 suppress_next_result_(false) { | |
322 set_delegate(delegate); | |
323 } | |
324 | |
325 InputMethodIBus::~InputMethodIBus() { | |
326 AbandonAllPendingKeyEvents(); | |
327 DestroyContext(); | |
328 | |
329 // Disconnect bus signals | |
330 g_signal_handlers_disconnect_by_func( | |
331 GetIBus(), reinterpret_cast<gpointer>(OnIBusConnectedThunk), this); | |
332 g_signal_handlers_disconnect_by_func( | |
333 GetIBus(), reinterpret_cast<gpointer>(OnIBusDisconnectedThunk), this); | |
334 } | |
335 | |
336 void InputMethodIBus::OnFocus() { | |
337 DCHECK(!widget_focused()); | |
338 InputMethodBase::OnFocus(); | |
339 UpdateContextFocusState(); | |
340 } | |
341 | |
342 void InputMethodIBus::OnBlur() { | |
343 DCHECK(widget_focused()); | |
344 ConfirmCompositionText(); | |
345 InputMethodBase::OnBlur(); | |
346 UpdateContextFocusState(); | |
347 } | |
348 | |
349 void InputMethodIBus::Init(Widget* widget) { | |
350 // Initializes the connection to ibus daemon. It may happen asynchronously, | |
351 // and as soon as the connection is established, the |context_| will be | |
352 // created automatically. | |
353 IBusBus* bus = GetIBus(); | |
354 | |
355 // connect bus signals | |
356 g_signal_connect(bus, "connected", | |
357 G_CALLBACK(OnIBusConnectedThunk), this); | |
358 g_signal_connect(bus, "disconnected", | |
359 G_CALLBACK(OnIBusDisconnectedThunk), this); | |
360 | |
361 // Creates the |context_| if the connection is already established. In such | |
362 // case, we will not get "connected" signal. | |
363 if (ibus_bus_is_connected(bus)) | |
364 CreateContext(); | |
365 | |
366 InputMethodBase::Init(widget); | |
367 } | |
368 | |
369 void InputMethodIBus::DispatchKeyEvent(const KeyEvent& key) { | |
370 DCHECK(key.type() == ui::ET_KEY_PRESSED || key.type() == ui::ET_KEY_RELEASED); | |
371 DCHECK(widget_focused()); | |
372 | |
373 guint32 ibus_keyval = 0; | |
374 guint32 ibus_keycode = 0; | |
375 guint32 ibus_state = 0; | |
376 IBusKeyEventFromViewsKeyEvent(key, &ibus_keyval, &ibus_keycode, &ibus_state); | |
377 | |
378 // If |context_| is not usable, then we can only dispatch the key event as is. | |
379 // We also dispatch the key event directly if the current text input type is | |
380 // ui::TEXT_INPUT_TYPE_PASSWORD, to bypass the input method. | |
381 // Note: We need to send the key event to ibus even if the |context_| is not | |
382 // enabled, so that ibus can have a chance to enable the |context_|. | |
383 if (!context_focused_ || | |
384 GetTextInputType() == ui::TEXT_INPUT_TYPE_PASSWORD) { | |
385 if (key.type() == ui::ET_KEY_PRESSED) | |
386 ProcessUnfilteredKeyPressEvent(key, ibus_keyval); | |
387 else | |
388 DispatchKeyEventPostIME(key); | |
389 return; | |
390 } | |
391 | |
392 PendingKeyEvent* pending_key = new PendingKeyEvent(this, key, ibus_keyval); | |
393 pending_key_events_.insert(pending_key); | |
394 | |
395 // Note: | |
396 // 1. We currently set timeout to -1, because ibus doesn't have a mechanism to | |
397 // associate input method results to corresponding key event, thus there is | |
398 // actually no way to abandon results generated by a specific key event. So we | |
399 // actually cannot abandon a specific key event and its result but accept | |
400 // following key events and their results. So a timeout to abandon a key event | |
401 // will not work. | |
402 // 2. We set GCancellable to NULL, because the operation of cancelling a async | |
403 // request also happens asynchronously, thus it's actually useless to us. | |
404 // | |
405 // The fundemental problem of ibus' async API is: it uses Glib's GIO API to | |
406 // realize async communication, but in fact, GIO API is specially designed for | |
407 // concurrent tasks, though it supports async communication as well, the API | |
408 // is much more complicated than an ordinary message based async communication | |
409 // API (such as Chrome's IPC). | |
410 // Thus it's very complicated, if not impossible, to implement a client that | |
411 // fully utilize asynchronous communication without potential problem. | |
412 ibus_input_context_process_key_event_async( | |
413 context_, | |
414 ibus_keyval, ibus_keycode, ibus_state, -1, NULL, | |
415 reinterpret_cast<GAsyncReadyCallback>(ProcessKeyEventDone), | |
416 pending_key); | |
417 | |
418 // We don't want to suppress the result generated by this key event, but it | |
419 // may cause problem. See comment in ResetContext() method. | |
420 suppress_next_result_ = false; | |
421 } | |
422 | |
423 void InputMethodIBus::OnTextInputTypeChanged(View* view) { | |
424 if (context_ && IsViewFocused(view)) { | |
425 ResetContext(); | |
426 UpdateContextFocusState(); | |
427 } | |
428 InputMethodBase::OnTextInputTypeChanged(view); | |
429 } | |
430 | |
431 void InputMethodIBus::OnCaretBoundsChanged(View* view) { | |
432 if (!context_focused_ || !IsViewFocused(view)) | |
433 return; | |
434 | |
435 // The current text input type should not be NONE if |context_| is focused. | |
436 DCHECK(!IsTextInputTypeNone()); | |
437 | |
438 gfx::Rect rect = GetTextInputClient()->GetCaretBounds(); | |
439 gfx::Point origin = rect.origin(); | |
440 gfx::Point end = gfx::Point(rect.right(), rect.bottom()); | |
441 | |
442 // We need to convert the origin and end points separately, in case the View | |
443 // is scaled. | |
444 View::ConvertPointToScreen(view, &origin); | |
445 View::ConvertPointToScreen(view, &end); | |
446 | |
447 // This function runs asynchronously. | |
448 ibus_input_context_set_cursor_location( | |
449 context_, origin.x(), origin.y(), | |
450 end.x() - origin.x(), end.y() - origin.y()); | |
451 } | |
452 | |
453 void InputMethodIBus::CancelComposition(View* view) { | |
454 if (context_focused_ && IsViewFocused(view)) | |
455 ResetContext(); | |
456 } | |
457 | |
458 std::string InputMethodIBus::GetInputLocale() { | |
459 // Not supported. | |
460 return std::string(""); | |
461 } | |
462 | |
463 base::i18n::TextDirection InputMethodIBus::GetInputTextDirection() { | |
464 // Not supported. | |
465 return base::i18n::UNKNOWN_DIRECTION; | |
466 } | |
467 | |
468 bool InputMethodIBus::IsActive() { | |
469 return true; | |
470 } | |
471 | |
472 // static | |
473 bool InputMethodIBus::IsInputMethodIBusEnabled() { | |
474 #if defined(TOUCH_UI) | |
475 return true; | |
476 #else | |
477 return inputmethod_ibus_enabled || | |
478 CommandLine::ForCurrentProcess()->HasSwitch( | |
479 kEnableInputMethodIBusSwitch); | |
480 #endif | |
481 } | |
482 | |
483 // static | |
484 void InputMethodIBus::SetEnableInputMethodIBus(bool enabled) { | |
485 inputmethod_ibus_enabled = enabled; | |
486 } | |
487 | |
488 void InputMethodIBus::OnWillChangeFocus(View* focused_before, View* focused) { | |
489 ConfirmCompositionText(); | |
490 } | |
491 | |
492 void InputMethodIBus::OnDidChangeFocus(View* focused_before, View* focused) { | |
493 UpdateContextFocusState(); | |
494 | |
495 // Force to update caret bounds, in case the View thinks that the caret | |
496 // bounds has not changed. | |
497 if (context_focused_) | |
498 OnCaretBoundsChanged(GetFocusedView()); | |
499 } | |
500 | |
501 void InputMethodIBus::CreateContext() { | |
502 DCHECK(!context_); | |
503 DCHECK(GetIBus()); | |
504 DCHECK(ibus_bus_is_connected(GetIBus())); | |
505 DCHECK(!pending_create_ic_request_); | |
506 | |
507 // Creates the input context asynchronously. | |
508 pending_create_ic_request_ = new PendingCreateICRequest( | |
509 this, &pending_create_ic_request_); | |
510 ibus_bus_create_input_context_async( | |
511 GetIBus(), "chrome", -1, NULL, | |
512 reinterpret_cast<GAsyncReadyCallback>(CreateInputContextDone), | |
513 pending_create_ic_request_); | |
514 } | |
515 | |
516 void InputMethodIBus::SetContext(IBusInputContext* ic) { | |
517 DCHECK(ic); | |
518 DCHECK(!context_); | |
519 context_ = ic; | |
520 | |
521 // connect input context signals | |
522 g_signal_connect(ic, "commit-text", | |
523 G_CALLBACK(OnCommitTextThunk), this); | |
524 g_signal_connect(ic, "forward-key-event", | |
525 G_CALLBACK(OnForwardKeyEventThunk), this); | |
526 g_signal_connect(ic, "update-preedit-text", | |
527 G_CALLBACK(OnUpdatePreeditTextThunk), this); | |
528 g_signal_connect(ic, "show-preedit-text", | |
529 G_CALLBACK(OnShowPreeditTextThunk), this); | |
530 g_signal_connect(ic, "hide-preedit-text", | |
531 G_CALLBACK(OnHidePreeditTextThunk), this); | |
532 g_signal_connect(ic, "destroy", | |
533 G_CALLBACK(OnDestroyThunk), this); | |
534 | |
535 // TODO(suzhe): support surrounding text. | |
536 guint32 caps = IBUS_CAP_PREEDIT_TEXT | IBUS_CAP_FOCUS; | |
537 ibus_input_context_set_capabilities(ic, caps); | |
538 | |
539 UpdateContextFocusState(); | |
540 OnInputMethodChanged(); | |
541 } | |
542 | |
543 void InputMethodIBus::DestroyContext() { | |
544 if (pending_create_ic_request_) { | |
545 DCHECK(!context_); | |
546 // |pending_create_ic_request_| will be deleted in CreateInputContextDone(). | |
547 pending_create_ic_request_->abandon(); | |
548 pending_create_ic_request_ = NULL; | |
549 } else if (context_) { | |
550 // ibus_proxy_destroy() will not really release the resource of |context_| | |
551 // object. We still need to handle "destroy" signal and call | |
552 // g_object_unref() there. | |
553 ibus_proxy_destroy(reinterpret_cast<IBusProxy *>(context_)); | |
554 DCHECK(!context_); | |
555 } | |
556 } | |
557 | |
558 void InputMethodIBus::ConfirmCompositionText() { | |
559 ui::TextInputClient* client = GetTextInputClient(); | |
560 if (client && client->HasCompositionText()) | |
561 client->ConfirmCompositionText(); | |
562 | |
563 ResetContext(); | |
564 } | |
565 | |
566 void InputMethodIBus::ResetContext() { | |
567 if (!context_focused_ || !GetTextInputClient()) | |
568 return; | |
569 | |
570 DCHECK(widget_focused()); | |
571 DCHECK(GetFocusedView()); | |
572 | |
573 // Because ibus runs in asynchronous mode, the input method may still send us | |
574 // results after sending out the reset request, so we use a flag to discard | |
575 // all results generated by previous key events. But because ibus does not | |
576 // have a mechanism to identify each key event and corresponding results, this | |
577 // approach will not work for some corner cases. For example if the user types | |
578 // very fast, then the next key event may come in before the |context_| is | |
579 // really reset. Then we actually cannot know whether or not the next | |
580 // result should be discard. | |
581 suppress_next_result_ = true; | |
582 | |
583 composition_.Clear(); | |
584 result_text_.clear(); | |
585 composing_text_ = false; | |
586 composition_changed_ = false; | |
587 | |
588 // We need to abandon all pending key events, but as above comment says, there | |
589 // is no reliable way to abandon all results generated by these abandoned key | |
590 // events. | |
591 AbandonAllPendingKeyEvents(); | |
592 | |
593 // This function runs asynchronously. | |
594 // Note: some input method engines may not support reset method, such as | |
595 // ibus-anthy. But as we control all input method engines by ourselves, we can | |
596 // make sure that all of the engines we are using support it correctly. | |
597 ibus_input_context_reset(context_); | |
598 | |
599 character_composer_.Reset(); | |
600 } | |
601 | |
602 void InputMethodIBus::UpdateContextFocusState() { | |
603 if (!context_) { | |
604 context_focused_ = false; | |
605 return; | |
606 } | |
607 | |
608 const bool old_context_focused = context_focused_; | |
609 // Use switch here in case we are going to add more text input types. | |
610 switch (GetTextInputType()) { | |
611 case ui::TEXT_INPUT_TYPE_NONE: | |
612 case ui::TEXT_INPUT_TYPE_PASSWORD: | |
613 context_focused_ = false; | |
614 break; | |
615 default: | |
616 context_focused_ = true; | |
617 break; | |
618 } | |
619 | |
620 // We only focus in |context_| when the focus is in a normal textfield. | |
621 // ibus_input_context_focus_{in|out}() run asynchronously. | |
622 if (old_context_focused && !context_focused_) | |
623 ibus_input_context_focus_out(context_); | |
624 else if (!old_context_focused && context_focused_) | |
625 ibus_input_context_focus_in(context_); | |
626 } | |
627 | |
628 void InputMethodIBus::ProcessKeyEventPostIME(const KeyEvent& key, | |
629 guint32 ibus_keyval, | |
630 bool handled) { | |
631 // If we get here without a focused text input client, then it means the key | |
632 // event is sent to the global ibus input context. | |
633 if (!GetTextInputClient()) { | |
634 DispatchKeyEventPostIME(key); | |
635 return; | |
636 } | |
637 | |
638 const View* old_focused_view = GetFocusedView(); | |
639 | |
640 // Same reason as above DCHECK. | |
641 DCHECK(old_focused_view); | |
642 | |
643 if (key.type() == ui::ET_KEY_PRESSED && handled) | |
644 ProcessFilteredKeyPressEvent(key); | |
645 | |
646 // In case the focus was changed by the key event. The |context_| should have | |
647 // been reset when the focused view changed. | |
648 if (old_focused_view != GetFocusedView()) | |
649 return; | |
650 | |
651 if (HasInputMethodResult()) | |
652 ProcessInputMethodResult(key, handled); | |
653 | |
654 // In case the focus was changed when sending input method results to the | |
655 // focused View. | |
656 if (old_focused_view != GetFocusedView()) | |
657 return; | |
658 | |
659 if (key.type() == ui::ET_KEY_PRESSED && !handled) | |
660 ProcessUnfilteredKeyPressEvent(key, ibus_keyval); | |
661 else if (key.type() == ui::ET_KEY_RELEASED) | |
662 DispatchKeyEventPostIME(key); | |
663 } | |
664 | |
665 void InputMethodIBus::ProcessFilteredKeyPressEvent(const KeyEvent& key) { | |
666 if (NeedInsertChar()) { | |
667 DispatchKeyEventPostIME(key); | |
668 } else { | |
669 KeyEvent key(ui::ET_KEY_PRESSED, ui::VKEY_PROCESSKEY, key.flags()); | |
670 DispatchKeyEventPostIME(key); | |
671 } | |
672 } | |
673 | |
674 void InputMethodIBus::ProcessUnfilteredKeyPressEvent(const KeyEvent& key, | |
675 guint32 ibus_keyval) { | |
676 const View* old_focused_view = GetFocusedView(); | |
677 DispatchKeyEventPostIME(key); | |
678 | |
679 // We shouldn't dispatch the character anymore if the key event caused focus | |
680 // change. | |
681 if (old_focused_view != GetFocusedView()) | |
682 return; | |
683 | |
684 // Process compose and dead keys | |
685 if (character_composer_.FilterKeyPress(ibus_keyval)) { | |
686 string16 composed = character_composer_.composed_character(); | |
687 if (!composed.empty()) { | |
688 ui::TextInputClient* client = GetTextInputClient(); | |
689 if (client) | |
690 client->InsertText(composed); | |
691 } | |
692 return; | |
693 } | |
694 // If a key event was not filtered by |context_| and |character_composer_|, | |
695 // then it means the key event didn't generate any result text. So we need | |
696 // to send corresponding character to the focused text input client. | |
697 | |
698 ui::TextInputClient* client = GetTextInputClient(); | |
699 char16 ch = key.GetCharacter(); | |
700 if (ch && client) | |
701 client->InsertChar(ch, key.flags()); | |
702 } | |
703 | |
704 void InputMethodIBus::ProcessInputMethodResult(const KeyEvent& key, | |
705 bool handled) { | |
706 ui::TextInputClient* client = GetTextInputClient(); | |
707 DCHECK(client); | |
708 | |
709 if (result_text_.length()) { | |
710 if (handled && NeedInsertChar()) { | |
711 for (string16::const_iterator i = result_text_.begin(); | |
712 i != result_text_.end(); ++i) { | |
713 client->InsertChar(*i, key.flags()); | |
714 } | |
715 } else { | |
716 client->InsertText(result_text_); | |
717 composing_text_ = false; | |
718 } | |
719 } | |
720 | |
721 if (composition_changed_ && !IsTextInputTypeNone()) { | |
722 if (composition_.text.length()) { | |
723 composing_text_ = true; | |
724 client->SetCompositionText(composition_); | |
725 } else if (result_text_.empty()) { | |
726 client->ClearCompositionText(); | |
727 } | |
728 } | |
729 | |
730 // We should not clear composition text here, as it may belong to the next | |
731 // composition session. | |
732 result_text_.clear(); | |
733 composition_changed_ = false; | |
734 } | |
735 | |
736 bool InputMethodIBus::NeedInsertChar() const { | |
737 return GetTextInputClient() && | |
738 (IsTextInputTypeNone() || | |
739 (!composing_text_ && result_text_.length() == 1)); | |
740 } | |
741 | |
742 bool InputMethodIBus::HasInputMethodResult() const { | |
743 return result_text_.length() || composition_changed_; | |
744 } | |
745 | |
746 void InputMethodIBus::SendFakeProcessKeyEvent(bool pressed) const { | |
747 KeyEvent key(pressed ? ui::ET_KEY_PRESSED : ui::ET_KEY_RELEASED, | |
748 ui::VKEY_PROCESSKEY, 0); | |
749 DispatchKeyEventPostIME(key); | |
750 } | |
751 | |
752 void InputMethodIBus::FinishPendingKeyEvent(PendingKeyEvent* pending_key) { | |
753 DCHECK(pending_key_events_.count(pending_key)); | |
754 | |
755 // |pending_key| will be deleted in ProcessKeyEventDone(). | |
756 pending_key_events_.erase(pending_key); | |
757 } | |
758 | |
759 void InputMethodIBus::AbandonAllPendingKeyEvents() { | |
760 for (std::set<PendingKeyEvent*>::iterator i = pending_key_events_.begin(); | |
761 i != pending_key_events_.end(); ++i) { | |
762 // The object will be deleted in ProcessKeyEventDone(). | |
763 (*i)->abandon(); | |
764 } | |
765 pending_key_events_.clear(); | |
766 } | |
767 | |
768 void InputMethodIBus::OnCommitText(IBusInputContext* context, IBusText* text) { | |
769 DCHECK_EQ(context_, context); | |
770 if (suppress_next_result_ || !text || !text->text) | |
771 return; | |
772 | |
773 // We need to receive input method result even if the text input type is | |
774 // ui::TEXT_INPUT_TYPE_NONE, to make sure we can always send correct | |
775 // character for each key event to the focused text input client. | |
776 if (!GetTextInputClient()) | |
777 return; | |
778 | |
779 string16 utf16_text(UTF8ToUTF16(text->text)); | |
780 | |
781 // Append the text to the buffer, because commit signal might be fired | |
782 // multiple times when processing a key event. | |
783 result_text_.append(utf16_text); | |
784 | |
785 // If we are not handling key event, do not bother sending text result if the | |
786 // focused text input client does not support text input. | |
787 if (pending_key_events_.empty() && !IsTextInputTypeNone()) { | |
788 SendFakeProcessKeyEvent(true); | |
789 GetTextInputClient()->InsertText(utf16_text); | |
790 SendFakeProcessKeyEvent(false); | |
791 result_text_.clear(); | |
792 } | |
793 } | |
794 | |
795 void InputMethodIBus::OnForwardKeyEvent(IBusInputContext* context, | |
796 guint keyval, | |
797 guint keycode, | |
798 guint state) { | |
799 DCHECK_EQ(context_, context); | |
800 | |
801 ui::KeyboardCode key_code = ui::VKEY_UNKNOWN; | |
802 #if defined(USE_AURA) || defined(TOUCH_UI) | |
803 key_code = ui::KeyboardCodeFromXKeysym(keyval); | |
804 #elif defined(TOOLKIT_USES_GTK) | |
805 key_code = ui::WindowsKeyCodeForGdkKeyCode(keyval); | |
806 #endif | |
807 | |
808 if (!key_code) | |
809 return; | |
810 | |
811 KeyEvent key(state & IBUS_RELEASE_MASK ? | |
812 ui::ET_KEY_RELEASED : ui::ET_KEY_PRESSED, | |
813 key_code, EventFlagsFromIBusState(state)); | |
814 | |
815 // It is not clear when the input method will forward us a fake key event. | |
816 // If there is a pending key event, then we may already received some input | |
817 // method results, so we dispatch this fake key event directly rather than | |
818 // calling ProcessKeyEventPostIME(), which will clear pending input method | |
819 // results. | |
820 if (key.type() == ui::ET_KEY_PRESSED) | |
821 ProcessUnfilteredKeyPressEvent(key, keyval); | |
822 else | |
823 DispatchKeyEventPostIME(key); | |
824 } | |
825 | |
826 void InputMethodIBus::OnShowPreeditText(IBusInputContext* context) { | |
827 DCHECK_EQ(context_, context); | |
828 if (suppress_next_result_ || IsTextInputTypeNone()) | |
829 return; | |
830 | |
831 composing_text_ = true; | |
832 } | |
833 | |
834 void InputMethodIBus::OnUpdatePreeditText(IBusInputContext* context, | |
835 IBusText* text, | |
836 guint cursor_pos, | |
837 gboolean visible) { | |
838 DCHECK_EQ(context_, context); | |
839 if (suppress_next_result_ || IsTextInputTypeNone()) | |
840 return; | |
841 | |
842 // |visible| argument is very confusing. For example, what's the correct | |
843 // behavior when: | |
844 // 1. OnUpdatePreeditText() is called with a text and visible == false, then | |
845 // 2. OnShowPreeditText() is called afterwards. | |
846 // | |
847 // If it's only for clearing the current preedit text, then why not just use | |
848 // OnHidePreeditText()? | |
849 if (!visible) { | |
850 OnHidePreeditText(context); | |
851 return; | |
852 } | |
853 | |
854 ExtractCompositionTextFromIBusPreedit(text, cursor_pos, &composition_); | |
855 | |
856 composition_changed_ = true; | |
857 | |
858 // In case OnShowPreeditText() is not called. | |
859 if (composition_.text.length()) | |
860 composing_text_ = true; | |
861 | |
862 // If we receive a composition text without pending key event, then we need to | |
863 // send it to the focused text input client directly. | |
864 if (pending_key_events_.empty()) { | |
865 SendFakeProcessKeyEvent(true); | |
866 GetTextInputClient()->SetCompositionText(composition_); | |
867 SendFakeProcessKeyEvent(false); | |
868 composition_changed_ = false; | |
869 composition_.Clear(); | |
870 } | |
871 } | |
872 | |
873 void InputMethodIBus::OnHidePreeditText(IBusInputContext* context) { | |
874 DCHECK_EQ(context_, context); | |
875 if (composition_.text.empty() || IsTextInputTypeNone()) | |
876 return; | |
877 | |
878 // Intentionally leaves |composing_text_| unchanged. | |
879 composition_changed_ = true; | |
880 composition_.Clear(); | |
881 | |
882 if (pending_key_events_.empty()) { | |
883 ui::TextInputClient* client = GetTextInputClient(); | |
884 if (client && client->HasCompositionText()) | |
885 client->ClearCompositionText(); | |
886 composition_changed_ = false; | |
887 } | |
888 } | |
889 | |
890 void InputMethodIBus::OnDestroy(IBusInputContext* context) { | |
891 DCHECK_EQ(context_, context); | |
892 g_object_unref(context_); | |
893 context_ = NULL; | |
894 context_focused_ = false; | |
895 | |
896 ConfirmCompositionText(); | |
897 | |
898 // We are dead, so we need to ask the client to stop relying on us. | |
899 // We cannot do it in DestroyContext(), because OnDestroy() may be called | |
900 // automatically. | |
901 OnInputMethodChanged(); | |
902 } | |
903 | |
904 void InputMethodIBus::OnIBusConnected(IBusBus* bus) { | |
905 DCHECK_EQ(GetIBus(), bus); | |
906 DCHECK(ibus_bus_is_connected(bus)); | |
907 | |
908 DestroyContext(); | |
909 CreateContext(); | |
910 } | |
911 | |
912 void InputMethodIBus::OnIBusDisconnected(IBusBus* bus) { | |
913 DCHECK_EQ(GetIBus(), bus); | |
914 | |
915 // TODO(suzhe): Make sure if we really do not need to handle this signal. | |
916 // And I'm actually wondering if ibus-daemon will release the resource of the | |
917 // |context_| correctly when the connection is lost. | |
918 } | |
919 | |
920 // static | |
921 IBusBus* InputMethodIBus::GetIBus() { | |
922 // Everything happens in UI thread, so we do not need to care about | |
923 // synchronization issue. | |
924 static IBusBus* ibus = NULL; | |
925 | |
926 if (!ibus) { | |
927 ibus_init(); | |
928 ibus = ibus_bus_new(); | |
929 DCHECK(ibus); | |
930 } | |
931 return ibus; | |
932 } | |
933 | |
934 // static | |
935 void InputMethodIBus::ProcessKeyEventDone(IBusInputContext* context, | |
936 GAsyncResult* res, | |
937 PendingKeyEvent* data) { | |
938 DCHECK(data); | |
939 DCHECK(!data->input_method() || | |
940 data->input_method()->context_ == context); | |
941 | |
942 gboolean handled = ibus_input_context_process_key_event_async_finish( | |
943 context, res, NULL); | |
944 data->ProcessPostIME(handled); | |
945 delete data; | |
946 } | |
947 | |
948 // static | |
949 void InputMethodIBus::CreateInputContextDone(IBusBus* bus, | |
950 GAsyncResult* res, | |
951 PendingCreateICRequest* data) { | |
952 DCHECK_EQ(GetIBus(), bus); | |
953 DCHECK(data); | |
954 IBusInputContext* ic = | |
955 ibus_bus_create_input_context_async_finish(bus, res, NULL); | |
956 if (ic) | |
957 data->StoreOrAbandonInputContext(ic); | |
958 delete data; | |
959 } | |
960 | |
961 } // namespace views | |
OLD | NEW |