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 #define XK_MISCELLANY 8 #include <X11/keysymdef.h> 9 #include <X11/X.h> 10 #include <X11/Xlib.h> 11 #include <X11/Xutil.h> 12 #undef FocusIn 13 #undef FocusOut 14 #include <map> 15 16 #include "ash/shell.h" 17 #include "base/logging.h" 18 #include "base/memory/scoped_ptr.h" 19 #include "base/strings/string_number_conversions.h" 20 #include "base/strings/string_util.h" 21 #include "base/strings/utf_string_conversions.h" 22 #include "chromeos/ime/candidate_window.h" 23 #include "chromeos/ime/component_extension_ime_manager.h" 24 #include "chromeos/ime/extension_ime_util.h" 25 #include "chromeos/ime/ibus_keymap.h" 26 #include "chromeos/ime/ibus_text.h" 27 #include "chromeos/ime/input_method_manager.h" 28 #include "ui/events/event.h" 29 #include "ui/events/keycodes/keyboard_code_conversion_x.h" 30 #include "ui/keyboard/keyboard_controller.h" 31 32 namespace chromeos { 33 const char* kErrorNotActive = "IME is not active"; 34 const char* kErrorWrongContext = "Context is not active"; 35 const char* kCandidateNotFound = "Candidate not found"; 36 const char* kEngineBusPrefix = "org.freedesktop.IBus."; 37 38 namespace { 39 40 // Notifies InputContextHandler that the preedit is changed. 41 void UpdatePreedit(const IBusText& ibus_text, 42 uint32 cursor_pos, 43 bool is_visible) { 44 IBusInputContextHandlerInterface* input_context = 45 IBusBridge::Get()->GetInputContextHandler(); 46 if (input_context) 47 input_context->UpdatePreeditText(ibus_text, cursor_pos, is_visible); 48 } 49 50 // Notifies CandidateWindowHandler that the auxilary text is changed. 51 // Auxilary text is usually footer text. 52 void UpdateAuxiliaryText(const std::string& text, bool is_visible) { 53 IBusPanelCandidateWindowHandlerInterface* candidate_window = 54 IBusBridge::Get()->GetCandidateWindowHandler(); 55 if (candidate_window) 56 candidate_window->UpdateAuxiliaryText(text, is_visible); 57 } 58 59 } // namespace 60 61 InputMethodEngine::InputMethodEngine() 62 : focused_(false), 63 active_(false), 64 context_id_(0), 65 next_context_id_(1), 66 aux_text_visible_(false), 67 observer_(NULL), 68 preedit_text_(new IBusText()), 69 preedit_cursor_(0), 70 candidate_window_(new input_method::CandidateWindow()), 71 window_visible_(false) {} 72 73 InputMethodEngine::~InputMethodEngine() { 74 input_method::InputMethodManager::Get()->RemoveInputMethodExtension(ibus_id_); 75 } 76 77 void InputMethodEngine::Initialize( 78 InputMethodEngineInterface::Observer* observer, 79 const char* engine_name, 80 const char* extension_id, 81 const char* engine_id, 82 const std::vector<std::string>& languages, 83 const std::vector<std::string>& layouts, 84 const GURL& options_page, 85 const GURL& input_view) { 86 DCHECK(observer) << "Observer must not be null."; 87 88 observer_ = observer; 89 engine_id_ = engine_id; 90 91 input_method::InputMethodManager* manager = 92 input_method::InputMethodManager::Get(); 93 ComponentExtensionIMEManager* comp_ext_ime_manager 94 = manager->GetComponentExtensionIMEManager(); 95 96 if (comp_ext_ime_manager->IsInitialized() && 97 comp_ext_ime_manager->IsWhitelistedExtension(extension_id)) { 98 ibus_id_ = comp_ext_ime_manager->GetId(extension_id, engine_id); 99 } else { 100 ibus_id_ = extension_ime_util::GetInputMethodID(extension_id, engine_id); 101 } 102 103 input_view_url_ = input_view; 104 105 manager->AddInputMethodExtension(ibus_id_, engine_name, layouts, languages, 106 options_page, input_view, this); 107 IBusBridge::Get()->SetEngineHandler(ibus_id_, this); 108 } 109 110 void InputMethodEngine::StartIme() { 111 input_method::InputMethodManager* manager = 112 input_method::InputMethodManager::Get(); 113 if (manager && ibus_id_ == manager->GetCurrentInputMethod().id()) 114 Enable(); 115 } 116 117 bool InputMethodEngine::SetComposition( 118 int context_id, 119 const char* text, 120 int selection_start, 121 int selection_end, 122 int cursor, 123 const std::vector<SegmentInfo>& segments, 124 std::string* error) { 125 if (!active_) { 126 *error = kErrorNotActive; 127 return false; 128 } 129 if (context_id != context_id_ || context_id_ == -1) { 130 *error = kErrorWrongContext; 131 return false; 132 } 133 134 preedit_cursor_ = cursor; 135 preedit_text_.reset(new IBusText()); 136 preedit_text_->set_text(text); 137 138 preedit_text_->set_selection_start(selection_start); 139 preedit_text_->set_selection_end(selection_end); 140 141 // TODO: Add support for displaying selected text in the composition string. 142 for (std::vector<SegmentInfo>::const_iterator segment = segments.begin(); 143 segment != segments.end(); ++segment) { 144 IBusText::UnderlineAttribute underline; 145 146 switch (segment->style) { 147 case SEGMENT_STYLE_UNDERLINE: 148 underline.type = IBusText::IBUS_TEXT_UNDERLINE_SINGLE; 149 break; 150 case SEGMENT_STYLE_DOUBLE_UNDERLINE: 151 underline.type = IBusText::IBUS_TEXT_UNDERLINE_DOUBLE; 152 break; 153 default: 154 continue; 155 } 156 157 underline.start_index = segment->start; 158 underline.end_index = segment->end; 159 preedit_text_->mutable_underline_attributes()->push_back(underline); 160 } 161 162 // TODO(nona): Makes focus out mode configuable, if necessary. 163 UpdatePreedit(*preedit_text_, preedit_cursor_, true); 164 return true; 165 } 166 167 bool InputMethodEngine::ClearComposition(int context_id, 168 std::string* error) { 169 if (!active_) { 170 *error = kErrorNotActive; 171 return false; 172 } 173 if (context_id != context_id_ || context_id_ == -1) { 174 *error = kErrorWrongContext; 175 return false; 176 } 177 178 preedit_cursor_ = 0; 179 preedit_text_.reset(new IBusText()); 180 UpdatePreedit(*preedit_text_, preedit_cursor_, false); 181 return true; 182 } 183 184 bool InputMethodEngine::CommitText(int context_id, const char* text, 185 std::string* error) { 186 if (!active_) { 187 // TODO: Commit the text anyways. 188 *error = kErrorNotActive; 189 return false; 190 } 191 if (context_id != context_id_ || context_id_ == -1) { 192 *error = kErrorWrongContext; 193 return false; 194 } 195 196 IBusBridge::Get()->GetInputContextHandler()->CommitText(text); 197 return true; 198 } 199 200 const InputMethodEngine::CandidateWindowProperty& 201 InputMethodEngine::GetCandidateWindowProperty() const { 202 return candidate_window_property_; 203 } 204 205 void InputMethodEngine::SetCandidateWindowProperty( 206 const CandidateWindowProperty& property) { 207 // Type conversion from InputMethodEngineInterface::CandidateWindowProperty to 208 // CandidateWindow::CandidateWindowProperty defined in chromeos/ime/. 209 input_method::CandidateWindow::CandidateWindowProperty dest_property; 210 dest_property.page_size = property.page_size; 211 dest_property.is_cursor_visible = property.is_cursor_visible; 212 dest_property.is_vertical = property.is_vertical; 213 dest_property.show_window_at_composition = 214 property.show_window_at_composition; 215 dest_property.cursor_position = 216 candidate_window_->GetProperty().cursor_position; 217 candidate_window_->SetProperty(dest_property); 218 candidate_window_property_ = property; 219 220 if (active_) { 221 IBusPanelCandidateWindowHandlerInterface* cw_handler = 222 IBusBridge::Get()->GetCandidateWindowHandler(); 223 if (cw_handler) 224 cw_handler->UpdateLookupTable(*candidate_window_, window_visible_); 225 } 226 } 227 228 bool InputMethodEngine::SetCandidateWindowVisible(bool visible, 229 std::string* error) { 230 if (!active_) { 231 *error = kErrorNotActive; 232 return false; 233 } 234 235 window_visible_ = visible; 236 IBusPanelCandidateWindowHandlerInterface* cw_handler = 237 IBusBridge::Get()->GetCandidateWindowHandler(); 238 if (cw_handler) 239 cw_handler->UpdateLookupTable(*candidate_window_, window_visible_); 240 return true; 241 } 242 243 void InputMethodEngine::SetCandidateWindowAuxText(const char* text) { 244 aux_text_.assign(text); 245 if (active_) { 246 // Should not show auxiliary text if the whole window visibility is false. 247 UpdateAuxiliaryText(aux_text_, window_visible_ && aux_text_visible_); 248 } 249 } 250 251 void InputMethodEngine::SetCandidateWindowAuxTextVisible(bool visible) { 252 aux_text_visible_ = visible; 253 if (active_) { 254 // Should not show auxiliary text if the whole window visibility is false. 255 UpdateAuxiliaryText(aux_text_, window_visible_ && aux_text_visible_); 256 } 257 } 258 259 bool InputMethodEngine::SetCandidates( 260 int context_id, 261 const std::vector<Candidate>& candidates, 262 std::string* error) { 263 if (!active_) { 264 *error = kErrorNotActive; 265 return false; 266 } 267 if (context_id != context_id_ || context_id_ == -1) { 268 *error = kErrorWrongContext; 269 return false; 270 } 271 272 // TODO: Nested candidates 273 candidate_ids_.clear(); 274 candidate_indexes_.clear(); 275 candidate_window_->mutable_candidates()->clear(); 276 for (std::vector<Candidate>::const_iterator ix = candidates.begin(); 277 ix != candidates.end(); ++ix) { 278 input_method::CandidateWindow::Entry entry; 279 entry.value = ix->value; 280 entry.label = ix->label; 281 entry.annotation = ix->annotation; 282 entry.description_title = ix->usage.title; 283 entry.description_body = ix->usage.body; 284 285 // Store a mapping from the user defined ID to the candidate index. 286 candidate_indexes_[ix->id] = candidate_ids_.size(); 287 candidate_ids_.push_back(ix->id); 288 289 candidate_window_->mutable_candidates()->push_back(entry); 290 } 291 if (active_) { 292 IBusPanelCandidateWindowHandlerInterface* cw_handler = 293 IBusBridge::Get()->GetCandidateWindowHandler(); 294 if (cw_handler) 295 cw_handler->UpdateLookupTable(*candidate_window_, window_visible_); 296 } 297 return true; 298 } 299 300 bool InputMethodEngine::SetCursorPosition(int context_id, int candidate_id, 301 std::string* error) { 302 if (!active_) { 303 *error = kErrorNotActive; 304 return false; 305 } 306 if (context_id != context_id_ || context_id_ == -1) { 307 *error = kErrorWrongContext; 308 return false; 309 } 310 311 std::map<int, int>::const_iterator position = 312 candidate_indexes_.find(candidate_id); 313 if (position == candidate_indexes_.end()) { 314 *error = kCandidateNotFound; 315 return false; 316 } 317 318 candidate_window_->set_cursor_position(position->second); 319 IBusPanelCandidateWindowHandlerInterface* cw_handler = 320 IBusBridge::Get()->GetCandidateWindowHandler(); 321 if (cw_handler) 322 cw_handler->UpdateLookupTable(*candidate_window_, window_visible_); 323 return true; 324 } 325 326 bool InputMethodEngine::SetMenuItems(const std::vector<MenuItem>& items) { 327 return UpdateMenuItems(items); 328 } 329 330 bool InputMethodEngine::UpdateMenuItems( 331 const std::vector<MenuItem>& items) { 332 if (!active_) 333 return false; 334 335 input_method::InputMethodPropertyList property_list; 336 for (std::vector<MenuItem>::const_iterator item = items.begin(); 337 item != items.end(); ++item) { 338 input_method::InputMethodProperty property; 339 MenuItemToProperty(*item, &property); 340 property_list.push_back(property); 341 } 342 343 input_method::InputMethodManager* manager = 344 input_method::InputMethodManager::Get(); 345 if (manager) 346 manager->SetCurrentInputMethodProperties(property_list); 347 348 return true; 349 } 350 351 bool InputMethodEngine::IsActive() const { 352 return active_; 353 } 354 355 void InputMethodEngine::KeyEventDone(input_method::KeyEventHandle* key_data, 356 bool handled) { 357 KeyEventDoneCallback* callback = 358 reinterpret_cast<KeyEventDoneCallback*>(key_data); 359 callback->Run(handled); 360 delete callback; 361 } 362 363 bool InputMethodEngine::DeleteSurroundingText(int context_id, 364 int offset, 365 size_t number_of_chars, 366 std::string* error) { 367 if (!active_) { 368 *error = kErrorNotActive; 369 return false; 370 } 371 if (context_id != context_id_ || context_id_ == -1) { 372 *error = kErrorWrongContext; 373 return false; 374 } 375 376 if (offset < 0 && static_cast<size_t>(-1 * offset) != size_t(number_of_chars)) 377 return false; // Currently we can only support preceding text. 378 379 // TODO(nona): Return false if there is ongoing composition. 380 381 IBusInputContextHandlerInterface* input_context = 382 IBusBridge::Get()->GetInputContextHandler(); 383 if (input_context) 384 input_context->DeleteSurroundingText(offset, number_of_chars); 385 386 return true; 387 } 388 389 void InputMethodEngine::FocusIn( 390 const IBusEngineHandlerInterface::InputContext& input_context) { 391 focused_ = true; 392 if (!active_) 393 return; 394 context_id_ = next_context_id_; 395 ++next_context_id_; 396 397 InputMethodEngineInterface::InputContext context; 398 context.id = context_id_; 399 switch (input_context.type) { 400 case ui::TEXT_INPUT_TYPE_SEARCH: 401 context.type = "search"; 402 break; 403 case ui::TEXT_INPUT_TYPE_TELEPHONE: 404 context.type = "tel"; 405 break; 406 case ui::TEXT_INPUT_TYPE_URL: 407 context.type = "url"; 408 break; 409 case ui::TEXT_INPUT_TYPE_EMAIL: 410 context.type = "email"; 411 break; 412 case ui::TEXT_INPUT_TYPE_NUMBER: 413 context.type = "number"; 414 break; 415 default: 416 context.type = "text"; 417 break; 418 } 419 420 observer_->OnFocus(context); 421 } 422 423 void InputMethodEngine::FocusOut() { 424 focused_ = false; 425 if (!active_) 426 return; 427 int context_id = context_id_; 428 context_id_ = -1; 429 observer_->OnBlur(context_id); 430 } 431 432 void InputMethodEngine::Enable() { 433 active_ = true; 434 observer_->OnActivate(engine_id_); 435 IBusEngineHandlerInterface::InputContext context(ui::TEXT_INPUT_TYPE_TEXT, 436 ui::TEXT_INPUT_MODE_DEFAULT); 437 FocusIn(context); 438 439 keyboard::KeyboardController* keyboard_controller = 440 ash::Shell::GetInstance()->keyboard_controller(); 441 if (keyboard_controller) { 442 keyboard_controller->SetOverrideContentUrl(input_view_url_); 443 } 444 } 445 446 void InputMethodEngine::Disable() { 447 active_ = false; 448 observer_->OnDeactivated(engine_id_); 449 450 keyboard::KeyboardController* keyboard_controller = 451 ash::Shell::GetInstance()->keyboard_controller(); 452 if (keyboard_controller) { 453 GURL empty_url; 454 keyboard_controller->SetOverrideContentUrl(empty_url); 455 } 456 } 457 458 void InputMethodEngine::PropertyActivate(const std::string& property_name) { 459 observer_->OnMenuItemActivated(engine_id_, property_name); 460 } 461 462 void InputMethodEngine::Reset() { 463 observer_->OnReset(engine_id_); 464 } 465 466 namespace { 467 void GetExtensionKeyboardEventFromKeyEvent( 468 const ui::KeyEvent& event, 469 InputMethodEngine::KeyboardEvent* ext_event) { 470 DCHECK(event.type() == ui::ET_KEY_RELEASED || 471 event.type() == ui::ET_KEY_PRESSED); 472 DCHECK(ext_event); 473 ext_event->type = (event.type() == ui::ET_KEY_RELEASED) ? "keyup" : "keydown"; 474 475 ext_event->code = event.code(); 476 ext_event->alt_key = event.IsAltDown(); 477 ext_event->ctrl_key = event.IsControlDown(); 478 ext_event->shift_key = event.IsShiftDown(); 479 ext_event->caps_lock = event.IsCapsLockDown(); 480 481 uint32 ibus_keyval = 0; 482 if (event.HasNativeEvent()) { 483 const base::NativeEvent& native_event = event.native_event(); 484 DCHECK(native_event); 485 486 XKeyEvent* x_key = &(static_cast<XEvent*>(native_event)->xkey); 487 KeySym keysym = NoSymbol; 488 ::XLookupString(x_key, NULL, 0, &keysym, NULL); 489 ibus_keyval = keysym; 490 } else { 491 // Convert ui::KeyEvent.key_code to DOM UIEvent key. 492 // XKeysymForWindowsKeyCode converts key_code to XKeySym, but it 493 // assumes US layout and does not care about CapLock state. 494 // 495 // TODO(komatsu): Support CapsLock states. 496 // TODO(komatsu): Support non-us keyboard layouts. 497 ibus_keyval = ui::XKeysymForWindowsKeyCode(event.key_code(), 498 event.IsShiftDown()); 499 } 500 ext_event->key = input_method::GetIBusKey(ibus_keyval); 501 } 502 } // namespace 503 504 void InputMethodEngine::ProcessKeyEvent( 505 const ui::KeyEvent& key_event, 506 const KeyEventDoneCallback& callback) { 507 508 KeyEventDoneCallback *handler = new KeyEventDoneCallback(); 509 *handler = callback; 510 511 KeyboardEvent ext_event; 512 GetExtensionKeyboardEventFromKeyEvent(key_event, &ext_event); 513 observer_->OnKeyEvent( 514 engine_id_, 515 ext_event, 516 reinterpret_cast<input_method::KeyEventHandle*>(handler)); 517 } 518 519 void InputMethodEngine::CandidateClicked(uint32 index) { 520 if (index > candidate_ids_.size()) { 521 return; 522 } 523 524 // Only left button click is supported at this moment. 525 observer_->OnCandidateClicked( 526 engine_id_, candidate_ids_.at(index), MOUSE_BUTTON_LEFT); 527 } 528 529 void InputMethodEngine::SetSurroundingText(const std::string& text, 530 uint32 cursor_pos, 531 uint32 anchor_pos) { 532 observer_->OnSurroundingTextChanged(engine_id_, 533 text, 534 static_cast<int>(cursor_pos), 535 static_cast<int>(anchor_pos)); 536 } 537 538 void InputMethodEngine::MenuItemToProperty( 539 const MenuItem& item, 540 input_method::InputMethodProperty* property) { 541 property->key = item.id; 542 543 if (item.modified & MENU_ITEM_MODIFIED_LABEL) { 544 property->label = item.label; 545 } 546 if (item.modified & MENU_ITEM_MODIFIED_VISIBLE) { 547 // TODO(nona): Implement it. 548 } 549 if (item.modified & MENU_ITEM_MODIFIED_CHECKED) { 550 property->is_selection_item_checked = item.checked; 551 } 552 if (item.modified & MENU_ITEM_MODIFIED_ENABLED) { 553 // TODO(nona): implement sensitive entry(crbug.com/140192). 554 } 555 if (item.modified & MENU_ITEM_MODIFIED_STYLE) { 556 if (!item.children.empty()) { 557 // TODO(nona): Implement it. 558 } else { 559 switch (item.style) { 560 case MENU_ITEM_STYLE_NONE: 561 NOTREACHED(); 562 break; 563 case MENU_ITEM_STYLE_CHECK: 564 // TODO(nona): Implement it. 565 break; 566 case MENU_ITEM_STYLE_RADIO: 567 property->is_selection_item = true; 568 break; 569 case MENU_ITEM_STYLE_SEPARATOR: 570 // TODO(nona): Implement it. 571 break; 572 } 573 } 574 } 575 576 // TODO(nona): Support item.children. 577 } 578 579 } // namespace chromeos 580