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