1 // Copyright 2013 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #include "chrome/browser/chromeos/input_method/input_method_engine.h" 6 7 #undef FocusIn 8 #undef FocusOut 9 #undef RootWindow 10 #include <map> 11 12 #include "ash/ime/input_method_menu_item.h" 13 #include "ash/ime/input_method_menu_manager.h" 14 #include "ash/shell.h" 15 #include "base/logging.h" 16 #include "base/memory/scoped_ptr.h" 17 #include "base/metrics/histogram.h" 18 #include "base/strings/string_number_conversions.h" 19 #include "base/strings/string_util.h" 20 #include "base/strings/stringprintf.h" 21 #include "base/strings/utf_string_conversions.h" 22 #include "chromeos/ime/component_extension_ime_manager.h" 23 #include "chromeos/ime/composition_text.h" 24 #include "chromeos/ime/extension_ime_util.h" 25 #include "chromeos/ime/input_method_manager.h" 26 #include "ui/aura/window.h" 27 #include "ui/aura/window_tree_host.h" 28 #include "ui/base/ime/candidate_window.h" 29 #include "ui/base/ime/chromeos/ime_keymap.h" 30 #include "ui/events/event.h" 31 #include "ui/events/event_processor.h" 32 #include "ui/events/keycodes/dom4/keycode_converter.h" 33 #include "ui/keyboard/keyboard_controller.h" 34 #include "ui/keyboard/keyboard_util.h" 35 36 namespace chromeos { 37 const char* kErrorNotActive = "IME is not active"; 38 const char* kErrorWrongContext = "Context is not active"; 39 const char* kCandidateNotFound = "Candidate not found"; 40 41 namespace { 42 43 // Notifies InputContextHandler that the composition is changed. 44 void UpdateComposition(const CompositionText& composition_text, 45 uint32 cursor_pos, 46 bool is_visible) { 47 IMEInputContextHandlerInterface* input_context = 48 IMEBridge::Get()->GetInputContextHandler(); 49 if (input_context) 50 input_context->UpdateCompositionText( 51 composition_text, cursor_pos, is_visible); 52 } 53 54 // Returns the length of characters of a UTF-8 string with unknown string 55 // length. Cannot apply faster algorithm to count characters in an utf-8 56 // string without knowing the string length, so just does a full scan. 57 size_t GetUtf8StringLength(const char* s) { 58 size_t ret = 0; 59 while (*s) { 60 if ((*s & 0xC0) != 0x80) 61 ret++; 62 ++s; 63 } 64 return ret; 65 } 66 67 std::string GetKeyFromEvent(const ui::KeyEvent& event) { 68 const std::string& code = event.code(); 69 if (StartsWithASCII(code, "Control", true)) 70 return "Ctrl"; 71 if (StartsWithASCII(code, "Shift", true)) 72 return "Shift"; 73 if (StartsWithASCII(code, "Alt", true)) 74 return "Alt"; 75 if (StartsWithASCII(code, "Arrow", true)) 76 return code.substr(5); 77 if (code == "Escape") 78 return "Esc"; 79 if (code == "Backspace" || code == "Tab" || 80 code == "Enter" || code == "CapsLock" || 81 code == "Power") 82 return code; 83 // Cases for media keys. 84 switch (event.key_code()) { 85 case ui::VKEY_BROWSER_BACK: 86 case ui::VKEY_F1: 87 return "HistoryBack"; 88 case ui::VKEY_BROWSER_FORWARD: 89 case ui::VKEY_F2: 90 return "HistoryForward"; 91 case ui::VKEY_BROWSER_REFRESH: 92 case ui::VKEY_F3: 93 return "BrowserRefresh"; 94 case ui::VKEY_MEDIA_LAUNCH_APP2: 95 case ui::VKEY_F4: 96 return "ChromeOSFullscreen"; 97 case ui::VKEY_MEDIA_LAUNCH_APP1: 98 case ui::VKEY_F5: 99 return "ChromeOSSwitchWindow"; 100 case ui::VKEY_BRIGHTNESS_DOWN: 101 case ui::VKEY_F6: 102 return "BrightnessDown"; 103 case ui::VKEY_BRIGHTNESS_UP: 104 case ui::VKEY_F7: 105 return "BrightnessUp"; 106 case ui::VKEY_VOLUME_MUTE: 107 case ui::VKEY_F8: 108 return "AudioVolumeMute"; 109 case ui::VKEY_VOLUME_DOWN: 110 case ui::VKEY_F9: 111 return "AudioVolumeDown"; 112 case ui::VKEY_VOLUME_UP: 113 case ui::VKEY_F10: 114 return "AudioVolumeUp"; 115 default: 116 break; 117 } 118 uint16 ch = 0; 119 // Ctrl+? cases, gets key value for Ctrl is not down. 120 if (event.flags() & ui::EF_CONTROL_DOWN) { 121 ui::KeyEvent event_no_ctrl(event.type(), 122 event.key_code(), 123 event.flags() ^ ui::EF_CONTROL_DOWN); 124 ch = event_no_ctrl.GetCharacter(); 125 } else { 126 ch = event.GetCharacter(); 127 } 128 return base::UTF16ToUTF8(base::string16(1, ch)); 129 } 130 131 void GetExtensionKeyboardEventFromKeyEvent( 132 const ui::KeyEvent& event, 133 InputMethodEngine::KeyboardEvent* ext_event) { 134 DCHECK(event.type() == ui::ET_KEY_RELEASED || 135 event.type() == ui::ET_KEY_PRESSED); 136 DCHECK(ext_event); 137 ext_event->type = (event.type() == ui::ET_KEY_RELEASED) ? "keyup" : "keydown"; 138 139 std::string dom_code = event.code(); 140 if (dom_code == ui::KeycodeConverter::InvalidKeyboardEventCode()) 141 dom_code = ui::KeyboardCodeToDomKeycode(event.key_code()); 142 ext_event->code = dom_code; 143 ext_event->key_code = static_cast<int>(event.key_code()); 144 ext_event->alt_key = event.IsAltDown(); 145 ext_event->ctrl_key = event.IsControlDown(); 146 ext_event->shift_key = event.IsShiftDown(); 147 ext_event->caps_lock = event.IsCapsLockDown(); 148 ext_event->key = GetKeyFromEvent(event); 149 } 150 151 } // namespace 152 153 InputMethodEngine::InputMethodEngine() 154 : current_input_type_(ui::TEXT_INPUT_TYPE_NONE), 155 context_id_(0), 156 next_context_id_(1), 157 composition_text_(new CompositionText()), 158 composition_cursor_(0), 159 candidate_window_(new ui::CandidateWindow()), 160 window_visible_(false), 161 sent_key_event_(NULL) { 162 } 163 164 InputMethodEngine::~InputMethodEngine() { 165 } 166 167 void InputMethodEngine::Initialize( 168 scoped_ptr<InputMethodEngineInterface::Observer> observer, 169 const char* extension_id) { 170 DCHECK(observer) << "Observer must not be null."; 171 172 // TODO(komatsu): It is probably better to set observer out of Initialize. 173 observer_ = observer.Pass(); 174 extension_id_ = extension_id; 175 } 176 177 const std::string& InputMethodEngine::GetActiveComponentId() const { 178 return active_component_id_; 179 } 180 181 bool InputMethodEngine::SetComposition( 182 int context_id, 183 const char* text, 184 int selection_start, 185 int selection_end, 186 int cursor, 187 const std::vector<SegmentInfo>& segments, 188 std::string* error) { 189 if (!IsActive()) { 190 *error = kErrorNotActive; 191 return false; 192 } 193 if (context_id != context_id_ || context_id_ == -1) { 194 *error = kErrorWrongContext; 195 return false; 196 } 197 198 composition_cursor_ = cursor; 199 composition_text_.reset(new CompositionText()); 200 composition_text_->set_text(base::UTF8ToUTF16(text)); 201 202 composition_text_->set_selection_start(selection_start); 203 composition_text_->set_selection_end(selection_end); 204 205 // TODO: Add support for displaying selected text in the composition string. 206 for (std::vector<SegmentInfo>::const_iterator segment = segments.begin(); 207 segment != segments.end(); ++segment) { 208 CompositionText::UnderlineAttribute underline; 209 210 switch (segment->style) { 211 case SEGMENT_STYLE_UNDERLINE: 212 underline.type = CompositionText::COMPOSITION_TEXT_UNDERLINE_SINGLE; 213 break; 214 case SEGMENT_STYLE_DOUBLE_UNDERLINE: 215 underline.type = CompositionText::COMPOSITION_TEXT_UNDERLINE_DOUBLE; 216 break; 217 default: 218 continue; 219 } 220 221 underline.start_index = segment->start; 222 underline.end_index = segment->end; 223 composition_text_->mutable_underline_attributes()->push_back(underline); 224 } 225 226 // TODO(nona): Makes focus out mode configuable, if necessary. 227 UpdateComposition(*composition_text_, composition_cursor_, true); 228 return true; 229 } 230 231 bool InputMethodEngine::ClearComposition(int context_id, 232 std::string* error) { 233 if (!IsActive()) { 234 *error = kErrorNotActive; 235 return false; 236 } 237 if (context_id != context_id_ || context_id_ == -1) { 238 *error = kErrorWrongContext; 239 return false; 240 } 241 242 composition_cursor_ = 0; 243 composition_text_.reset(new CompositionText()); 244 UpdateComposition(*composition_text_, composition_cursor_, false); 245 return true; 246 } 247 248 bool InputMethodEngine::CommitText(int context_id, const char* text, 249 std::string* error) { 250 if (!IsActive()) { 251 // TODO: Commit the text anyways. 252 *error = kErrorNotActive; 253 return false; 254 } 255 if (context_id != context_id_ || context_id_ == -1) { 256 *error = kErrorWrongContext; 257 return false; 258 } 259 260 IMEBridge::Get()->GetInputContextHandler()->CommitText(text); 261 262 // Records histograms for committed characters. 263 if (!composition_text_->text().empty()) { 264 size_t len = GetUtf8StringLength(text); 265 UMA_HISTOGRAM_CUSTOM_COUNTS("InputMethod.CommitLength", 266 len, 1, 25, 25); 267 } 268 return true; 269 } 270 271 bool InputMethodEngine::SendKeyEvents( 272 int context_id, 273 const std::vector<KeyboardEvent>& events) { 274 if (!IsActive()) { 275 return false; 276 } 277 // context_id == 0, means sending key events to non-input field. 278 // context_id_ == -1, means the focus is not in an input field. 279 if (context_id != 0 && (context_id != context_id_ || context_id_ == -1)) { 280 return false; 281 } 282 283 ui::EventProcessor* dispatcher = 284 ash::Shell::GetPrimaryRootWindow()->GetHost()->event_processor(); 285 286 for (size_t i = 0; i < events.size(); ++i) { 287 const KeyboardEvent& event = events[i]; 288 const ui::EventType type = 289 (event.type == "keyup") ? ui::ET_KEY_RELEASED : ui::ET_KEY_PRESSED; 290 ui::KeyboardCode key_code = static_cast<ui::KeyboardCode>(event.key_code); 291 if (key_code == ui::VKEY_UNKNOWN) 292 key_code = ui::DomKeycodeToKeyboardCode(event.code); 293 294 int flags = ui::EF_NONE; 295 flags |= event.alt_key ? ui::EF_ALT_DOWN : ui::EF_NONE; 296 flags |= event.ctrl_key ? ui::EF_CONTROL_DOWN : ui::EF_NONE; 297 flags |= event.shift_key ? ui::EF_SHIFT_DOWN : ui::EF_NONE; 298 flags |= event.caps_lock ? ui::EF_CAPS_LOCK_DOWN : ui::EF_NONE; 299 300 ui::KeyEvent ui_event(type, 301 key_code, 302 event.code, 303 flags); 304 // 4-bytes UTF-8 string is at least 2-characters UTF-16 string. 305 // And Key char can only be single UTF-16 character. 306 if (!event.key.empty() && event.key.size() < 4) { 307 base::string16 key_char = base::UTF8ToUTF16(event.key); 308 if (key_char.size() == 1) 309 ui_event.set_character(key_char[0]); 310 } 311 base::AutoReset<const ui::KeyEvent*> reset_sent_key(&sent_key_event_, 312 &ui_event); 313 ui::EventDispatchDetails details = dispatcher->OnEventFromSource(&ui_event); 314 if (details.dispatcher_destroyed) 315 break; 316 } 317 318 return true; 319 } 320 321 const InputMethodEngine::CandidateWindowProperty& 322 InputMethodEngine::GetCandidateWindowProperty() const { 323 return candidate_window_property_; 324 } 325 326 void InputMethodEngine::SetCandidateWindowProperty( 327 const CandidateWindowProperty& property) { 328 // Type conversion from InputMethodEngineInterface::CandidateWindowProperty to 329 // CandidateWindow::CandidateWindowProperty defined in chromeos/ime/. 330 ui::CandidateWindow::CandidateWindowProperty dest_property; 331 dest_property.page_size = property.page_size; 332 dest_property.is_cursor_visible = property.is_cursor_visible; 333 dest_property.is_vertical = property.is_vertical; 334 dest_property.show_window_at_composition = 335 property.show_window_at_composition; 336 dest_property.cursor_position = 337 candidate_window_->GetProperty().cursor_position; 338 dest_property.auxiliary_text = property.auxiliary_text; 339 dest_property.is_auxiliary_text_visible = property.is_auxiliary_text_visible; 340 341 candidate_window_->SetProperty(dest_property); 342 candidate_window_property_ = property; 343 344 if (IsActive()) { 345 IMECandidateWindowHandlerInterface* cw_handler = 346 IMEBridge::Get()->GetCandidateWindowHandler(); 347 if (cw_handler) 348 cw_handler->UpdateLookupTable(*candidate_window_, window_visible_); 349 } 350 } 351 352 bool InputMethodEngine::SetCandidateWindowVisible(bool visible, 353 std::string* error) { 354 if (!IsActive()) { 355 *error = kErrorNotActive; 356 return false; 357 } 358 359 window_visible_ = visible; 360 IMECandidateWindowHandlerInterface* cw_handler = 361 IMEBridge::Get()->GetCandidateWindowHandler(); 362 if (cw_handler) 363 cw_handler->UpdateLookupTable(*candidate_window_, window_visible_); 364 return true; 365 } 366 367 bool InputMethodEngine::SetCandidates( 368 int context_id, 369 const std::vector<Candidate>& candidates, 370 std::string* error) { 371 if (!IsActive()) { 372 *error = kErrorNotActive; 373 return false; 374 } 375 if (context_id != context_id_ || context_id_ == -1) { 376 *error = kErrorWrongContext; 377 return false; 378 } 379 380 // TODO: Nested candidates 381 candidate_ids_.clear(); 382 candidate_indexes_.clear(); 383 candidate_window_->mutable_candidates()->clear(); 384 for (std::vector<Candidate>::const_iterator ix = candidates.begin(); 385 ix != candidates.end(); ++ix) { 386 ui::CandidateWindow::Entry entry; 387 entry.value = base::UTF8ToUTF16(ix->value); 388 entry.label = base::UTF8ToUTF16(ix->label); 389 entry.annotation = base::UTF8ToUTF16(ix->annotation); 390 entry.description_title = base::UTF8ToUTF16(ix->usage.title); 391 entry.description_body = base::UTF8ToUTF16(ix->usage.body); 392 393 // Store a mapping from the user defined ID to the candidate index. 394 candidate_indexes_[ix->id] = candidate_ids_.size(); 395 candidate_ids_.push_back(ix->id); 396 397 candidate_window_->mutable_candidates()->push_back(entry); 398 } 399 if (IsActive()) { 400 IMECandidateWindowHandlerInterface* cw_handler = 401 IMEBridge::Get()->GetCandidateWindowHandler(); 402 if (cw_handler) 403 cw_handler->UpdateLookupTable(*candidate_window_, window_visible_); 404 } 405 return true; 406 } 407 408 bool InputMethodEngine::SetCursorPosition(int context_id, int candidate_id, 409 std::string* error) { 410 if (!IsActive()) { 411 *error = kErrorNotActive; 412 return false; 413 } 414 if (context_id != context_id_ || context_id_ == -1) { 415 *error = kErrorWrongContext; 416 return false; 417 } 418 419 std::map<int, int>::const_iterator position = 420 candidate_indexes_.find(candidate_id); 421 if (position == candidate_indexes_.end()) { 422 *error = kCandidateNotFound; 423 return false; 424 } 425 426 candidate_window_->set_cursor_position(position->second); 427 IMECandidateWindowHandlerInterface* cw_handler = 428 IMEBridge::Get()->GetCandidateWindowHandler(); 429 if (cw_handler) 430 cw_handler->UpdateLookupTable(*candidate_window_, window_visible_); 431 return true; 432 } 433 434 bool InputMethodEngine::SetMenuItems(const std::vector<MenuItem>& items) { 435 return UpdateMenuItems(items); 436 } 437 438 bool InputMethodEngine::UpdateMenuItems( 439 const std::vector<MenuItem>& items) { 440 if (!IsActive()) 441 return false; 442 443 ash::ime::InputMethodMenuItemList menu_item_list; 444 for (std::vector<MenuItem>::const_iterator item = items.begin(); 445 item != items.end(); ++item) { 446 ash::ime::InputMethodMenuItem property; 447 MenuItemToProperty(*item, &property); 448 menu_item_list.push_back(property); 449 } 450 451 ash::ime::InputMethodMenuManager::GetInstance()-> 452 SetCurrentInputMethodMenuItemList( 453 menu_item_list); 454 return true; 455 } 456 457 bool InputMethodEngine::IsActive() const { 458 return !active_component_id_.empty(); 459 } 460 461 bool InputMethodEngine::DeleteSurroundingText(int context_id, 462 int offset, 463 size_t number_of_chars, 464 std::string* error) { 465 if (!IsActive()) { 466 *error = kErrorNotActive; 467 return false; 468 } 469 if (context_id != context_id_ || context_id_ == -1) { 470 *error = kErrorWrongContext; 471 return false; 472 } 473 474 if (offset < 0 && static_cast<size_t>(-1 * offset) != size_t(number_of_chars)) 475 return false; // Currently we can only support preceding text. 476 477 // TODO(nona): Return false if there is ongoing composition. 478 479 IMEInputContextHandlerInterface* input_context = 480 IMEBridge::Get()->GetInputContextHandler(); 481 if (input_context) 482 input_context->DeleteSurroundingText(offset, number_of_chars); 483 484 return true; 485 } 486 487 void InputMethodEngine::HideInputView() { 488 keyboard::KeyboardController* keyboard_controller = 489 keyboard::KeyboardController::GetInstance(); 490 if (keyboard_controller) { 491 keyboard_controller->HideKeyboard( 492 keyboard::KeyboardController::HIDE_REASON_MANUAL); 493 } 494 } 495 496 void InputMethodEngine::EnableInputView() { 497 keyboard::SetOverrideContentUrl(input_method::InputMethodManager::Get() 498 ->GetActiveIMEState() 499 ->GetCurrentInputMethod() 500 .input_view_url()); 501 keyboard::KeyboardController* keyboard_controller = 502 keyboard::KeyboardController::GetInstance(); 503 if (keyboard_controller) 504 keyboard_controller->Reload(); 505 } 506 507 void InputMethodEngine::FocusIn( 508 const IMEEngineHandlerInterface::InputContext& input_context) { 509 current_input_type_ = input_context.type; 510 511 if (!IsActive() || current_input_type_ == ui::TEXT_INPUT_TYPE_NONE) 512 return; 513 514 context_id_ = next_context_id_; 515 ++next_context_id_; 516 517 InputMethodEngineInterface::InputContext context; 518 context.id = context_id_; 519 switch (current_input_type_) { 520 case ui::TEXT_INPUT_TYPE_SEARCH: 521 context.type = "search"; 522 break; 523 case ui::TEXT_INPUT_TYPE_TELEPHONE: 524 context.type = "tel"; 525 break; 526 case ui::TEXT_INPUT_TYPE_URL: 527 context.type = "url"; 528 break; 529 case ui::TEXT_INPUT_TYPE_EMAIL: 530 context.type = "email"; 531 break; 532 case ui::TEXT_INPUT_TYPE_NUMBER: 533 context.type = "number"; 534 break; 535 case ui::TEXT_INPUT_TYPE_PASSWORD: 536 context.type = "password"; 537 break; 538 default: 539 context.type = "text"; 540 break; 541 } 542 543 observer_->OnFocus(context); 544 } 545 546 void InputMethodEngine::FocusOut() { 547 if (!IsActive() || current_input_type_ == ui::TEXT_INPUT_TYPE_NONE) 548 return; 549 550 current_input_type_ = ui::TEXT_INPUT_TYPE_NONE; 551 552 int context_id = context_id_; 553 context_id_ = -1; 554 observer_->OnBlur(context_id); 555 } 556 557 void InputMethodEngine::Enable(const std::string& component_id) { 558 DCHECK(!component_id.empty()); 559 active_component_id_ = component_id; 560 observer_->OnActivate(component_id); 561 current_input_type_ = IMEBridge::Get()->GetCurrentTextInputType(); 562 FocusIn(IMEEngineHandlerInterface::InputContext( 563 current_input_type_, ui::TEXT_INPUT_MODE_DEFAULT)); 564 EnableInputView(); 565 } 566 567 void InputMethodEngine::Disable() { 568 active_component_id_.clear(); 569 observer_->OnDeactivated(active_component_id_); 570 } 571 572 void InputMethodEngine::PropertyActivate(const std::string& property_name) { 573 observer_->OnMenuItemActivated(active_component_id_, property_name); 574 } 575 576 void InputMethodEngine::Reset() { 577 observer_->OnReset(active_component_id_); 578 } 579 580 void InputMethodEngine::ProcessKeyEvent( 581 const ui::KeyEvent& key_event, 582 const KeyEventDoneCallback& callback) { 583 584 KeyEventDoneCallback *handler = new KeyEventDoneCallback(); 585 *handler = callback; 586 587 KeyboardEvent ext_event; 588 GetExtensionKeyboardEventFromKeyEvent(key_event, &ext_event); 589 590 // If the given key event is equal to the key event sent by 591 // SendKeyEvents, this engine ID is propagated to the extension IME. 592 // Note, this check relies on that ui::KeyEvent is propagated as 593 // reference without copying. 594 if (&key_event == sent_key_event_) 595 ext_event.extension_id = extension_id_; 596 597 observer_->OnKeyEvent( 598 active_component_id_, 599 ext_event, 600 reinterpret_cast<input_method::KeyEventHandle*>(handler)); 601 } 602 603 void InputMethodEngine::CandidateClicked(uint32 index) { 604 if (index > candidate_ids_.size()) { 605 return; 606 } 607 608 // Only left button click is supported at this moment. 609 observer_->OnCandidateClicked( 610 active_component_id_, candidate_ids_.at(index), MOUSE_BUTTON_LEFT); 611 } 612 613 void InputMethodEngine::SetSurroundingText(const std::string& text, 614 uint32 cursor_pos, 615 uint32 anchor_pos) { 616 observer_->OnSurroundingTextChanged(active_component_id_, 617 text, 618 static_cast<int>(cursor_pos), 619 static_cast<int>(anchor_pos)); 620 } 621 622 // TODO(uekawa): rename this method to a more reasonable name. 623 void InputMethodEngine::MenuItemToProperty( 624 const MenuItem& item, 625 ash::ime::InputMethodMenuItem* property) { 626 property->key = item.id; 627 628 if (item.modified & MENU_ITEM_MODIFIED_LABEL) { 629 property->label = item.label; 630 } 631 if (item.modified & MENU_ITEM_MODIFIED_VISIBLE) { 632 // TODO(nona): Implement it. 633 } 634 if (item.modified & MENU_ITEM_MODIFIED_CHECKED) { 635 property->is_selection_item_checked = item.checked; 636 } 637 if (item.modified & MENU_ITEM_MODIFIED_ENABLED) { 638 // TODO(nona): implement sensitive entry(crbug.com/140192). 639 } 640 if (item.modified & MENU_ITEM_MODIFIED_STYLE) { 641 if (!item.children.empty()) { 642 // TODO(nona): Implement it. 643 } else { 644 switch (item.style) { 645 case MENU_ITEM_STYLE_NONE: 646 NOTREACHED(); 647 break; 648 case MENU_ITEM_STYLE_CHECK: 649 // TODO(nona): Implement it. 650 break; 651 case MENU_ITEM_STYLE_RADIO: 652 property->is_selection_item = true; 653 break; 654 case MENU_ITEM_STYLE_SEPARATOR: 655 // TODO(nona): Implement it. 656 break; 657 } 658 } 659 } 660 661 // TODO(nona): Support item.children. 662 } 663 664 } // namespace chromeos 665