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