1 // Copyright (c) 2012 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_ibus.h" 6 7 #define XK_MISCELLANY 8 #include <X11/keysymdef.h> 9 #include <map> 10 11 #include "base/logging.h" 12 #include "base/memory/scoped_ptr.h" 13 #include "base/strings/string_number_conversions.h" 14 #include "base/strings/string_util.h" 15 #include "base/strings/utf_string_conversions.h" 16 #include "chromeos/dbus/dbus_thread_manager.h" 17 #include "chromeos/dbus/ibus/ibus_client.h" 18 #include "chromeos/dbus/ibus/ibus_component.h" 19 #include "chromeos/dbus/ibus/ibus_engine_factory_service.h" 20 #include "chromeos/dbus/ibus/ibus_engine_service.h" 21 #include "chromeos/dbus/ibus/ibus_lookup_table.h" 22 #include "chromeos/dbus/ibus/ibus_property.h" 23 #include "chromeos/dbus/ibus/ibus_text.h" 24 #include "chromeos/ime/component_extension_ime_manager.h" 25 #include "chromeos/ime/extension_ime_util.h" 26 #include "chromeos/ime/ibus_keymap.h" 27 #include "chromeos/ime/input_method_manager.h" 28 #include "dbus/object_path.h" 29 30 namespace chromeos { 31 const char* kErrorNotActive = "IME is not active"; 32 const char* kErrorWrongContext = "Context is not active"; 33 const char* kCandidateNotFound = "Candidate not found"; 34 const char* kEngineBusPrefix = "org.freedesktop.IBus."; 35 36 namespace { 37 const uint32 kIBusAltKeyMask = 1 << 3; 38 const uint32 kIBusCtrlKeyMask = 1 << 2; 39 const uint32 kIBusShiftKeyMask = 1 << 0; 40 const uint32 kIBusCapsLockMask = 1 << 1; 41 const uint32 kIBusKeyReleaseMask = 1 << 30; 42 } 43 44 InputMethodEngineIBus::InputMethodEngineIBus() 45 : focused_(false), 46 active_(false), 47 context_id_(0), 48 next_context_id_(1), 49 aux_text_(new IBusText()), 50 aux_text_visible_(false), 51 observer_(NULL), 52 preedit_text_(new IBusText()), 53 preedit_cursor_(0), 54 component_(new IBusComponent()), 55 table_(new IBusLookupTable()), 56 window_visible_(false), 57 weak_ptr_factory_(this) { 58 } 59 60 InputMethodEngineIBus::~InputMethodEngineIBus() { 61 input_method::InputMethodManager::Get()->RemoveInputMethodExtension(ibus_id_); 62 63 // Do not unset engine before removing input method extension, above function 64 // may call reset function of engine object. 65 // TODO(nona): Call Reset manually here and remove relevant code from 66 // InputMethodManager once ibus-daemon is gone. (crbug.com/158273) 67 if (!object_path_.value().empty()) { 68 GetCurrentService()->UnsetEngine(this); 69 DBusThreadManager::Get()->RemoveIBusEngineService(object_path_); 70 } 71 } 72 73 void InputMethodEngineIBus::Initialize( 74 InputMethodEngine::Observer* observer, 75 const char* engine_name, 76 const char* extension_id, 77 const char* engine_id, 78 const char* description, 79 const std::vector<std::string>& languages, 80 const std::vector<std::string>& layouts, 81 const GURL& options_page, 82 std::string* error) { 83 DCHECK(observer) << "Observer must not be null."; 84 85 observer_ = observer; 86 engine_id_ = engine_id; 87 88 input_method::InputMethodManager* manager = 89 input_method::InputMethodManager::Get(); 90 ComponentExtensionIMEManager* comp_ext_ime_manager 91 = manager->GetComponentExtensionIMEManager(); 92 93 if (comp_ext_ime_manager->IsInitialized() && 94 comp_ext_ime_manager->IsWhitelistedExtension(extension_id)) { 95 ibus_id_ = comp_ext_ime_manager->GetId(extension_id, engine_id); 96 } else { 97 ibus_id_ = extension_ime_util::GetInputMethodID(extension_id, engine_id); 98 } 99 100 component_.reset(new IBusComponent()); 101 component_->set_name(std::string(kEngineBusPrefix) + std::string(engine_id)); 102 component_->set_description(description); 103 component_->set_author(engine_name); 104 105 // TODO(nona): Remove IBusComponent once ibus is gone. 106 chromeos::IBusComponent::EngineDescription engine_desc; 107 engine_desc.engine_id = ibus_id_; 108 engine_desc.display_name = description; 109 engine_desc.description = description; 110 engine_desc.language_code = (languages.empty()) ? "" : languages[0]; 111 engine_desc.author = ibus_id_; 112 113 component_->mutable_engine_description()->push_back(engine_desc); 114 manager->AddInputMethodExtension(ibus_id_, engine_name, layouts, languages, 115 options_page, this); 116 // If connection is avaiable, register component. If there are no connection 117 // to ibus-daemon, OnConnected callback will register component instead. 118 if (IsConnected()) 119 RegisterComponent(); 120 } 121 122 void InputMethodEngineIBus::StartIme() { 123 input_method::InputMethodManager* manager = 124 input_method::InputMethodManager::Get(); 125 if (manager && ibus_id_ == manager->GetCurrentInputMethod().id()) 126 Enable(); 127 } 128 129 bool InputMethodEngineIBus::SetComposition( 130 int context_id, 131 const char* text, 132 int selection_start, 133 int selection_end, 134 int cursor, 135 const std::vector<SegmentInfo>& segments, 136 std::string* error) { 137 if (!active_) { 138 *error = kErrorNotActive; 139 return false; 140 } 141 if (context_id != context_id_ || context_id_ == -1) { 142 *error = kErrorWrongContext; 143 return false; 144 } 145 146 preedit_cursor_ = cursor; 147 preedit_text_.reset(new IBusText()); 148 preedit_text_->set_text(text); 149 150 preedit_text_->mutable_selection_attributes()->clear(); 151 IBusText::SelectionAttribute selection; 152 selection.start_index = selection_start; 153 selection.end_index = selection_end; 154 preedit_text_->mutable_selection_attributes()->push_back(selection); 155 156 // TODO: Add support for displaying selected text in the composition string. 157 for (std::vector<SegmentInfo>::const_iterator segment = segments.begin(); 158 segment != segments.end(); ++segment) { 159 IBusText::UnderlineAttribute underline; 160 161 switch (segment->style) { 162 case SEGMENT_STYLE_UNDERLINE: 163 underline.type = IBusText::IBUS_TEXT_UNDERLINE_SINGLE; 164 break; 165 case SEGMENT_STYLE_DOUBLE_UNDERLINE: 166 underline.type = IBusText::IBUS_TEXT_UNDERLINE_DOUBLE; 167 break; 168 default: 169 continue; 170 } 171 172 underline.start_index = segment->start; 173 underline.end_index = segment->end; 174 preedit_text_->mutable_underline_attributes()->push_back(underline); 175 } 176 177 // TODO(nona): Makes focus out mode configuable, if necessary. 178 GetCurrentService()->UpdatePreedit( 179 *preedit_text_.get(), 180 preedit_cursor_, 181 true, 182 chromeos::IBusEngineService::IBUS_ENGINE_PREEEDIT_FOCUS_OUT_MODE_COMMIT); 183 return true; 184 } 185 186 bool InputMethodEngineIBus::ClearComposition(int context_id, 187 std::string* error) { 188 if (!active_) { 189 *error = kErrorNotActive; 190 return false; 191 } 192 if (context_id != context_id_ || context_id_ == -1) { 193 *error = kErrorWrongContext; 194 return false; 195 } 196 197 preedit_cursor_ = 0; 198 preedit_text_.reset(new IBusText()); 199 GetCurrentService()->UpdatePreedit( 200 *preedit_text_.get(), 201 0, 202 false, 203 chromeos::IBusEngineService::IBUS_ENGINE_PREEEDIT_FOCUS_OUT_MODE_COMMIT); 204 return true; 205 } 206 207 bool InputMethodEngineIBus::CommitText(int context_id, const char* text, 208 std::string* error) { 209 if (!active_) { 210 // TODO: Commit the text anyways. 211 *error = kErrorNotActive; 212 return false; 213 } 214 if (context_id != context_id_ || context_id_ == -1) { 215 *error = kErrorWrongContext; 216 return false; 217 } 218 219 GetCurrentService()->CommitText(text); 220 return true; 221 } 222 223 bool InputMethodEngineIBus::SetCandidateWindowVisible(bool visible, 224 std::string* error) { 225 if (!active_) { 226 *error = kErrorNotActive; 227 return false; 228 } 229 230 window_visible_ = visible; 231 GetCurrentService()->UpdateLookupTable(*table_.get(), window_visible_); 232 return true; 233 } 234 235 void InputMethodEngineIBus::SetCandidateWindowCursorVisible(bool visible) { 236 table_->set_is_cursor_visible(visible); 237 // IBus shows candidates on a page where the cursor is placed, so we need to 238 // set the cursor position appropriately so IBus shows the right page. 239 // In the case that the cursor is not visible, we always show the first page. 240 // This trick works because only extension IMEs use this method and extension 241 // IMEs do not depend on the pagination feature of IBus. 242 if (!visible) 243 table_->set_cursor_position(0); 244 if (active_) 245 GetCurrentService()->UpdateLookupTable(*table_.get(), window_visible_); 246 } 247 248 void InputMethodEngineIBus::SetCandidateWindowVertical(bool vertical) { 249 table_->set_orientation(vertical ? IBusLookupTable::VERTICAL : 250 IBusLookupTable::HORIZONTAL); 251 if (active_) 252 GetCurrentService()->UpdateLookupTable(*table_.get(), window_visible_); 253 } 254 255 void InputMethodEngineIBus::SetCandidateWindowPageSize(int size) { 256 table_->set_page_size(size); 257 if (active_) 258 GetCurrentService()->UpdateLookupTable(*table_.get(), window_visible_); 259 } 260 261 void InputMethodEngineIBus::SetCandidateWindowAuxText(const char* text) { 262 aux_text_->set_text(text); 263 if (active_) { 264 // Should not show auxiliary text if the whole window visibility is false. 265 GetCurrentService()->UpdateAuxiliaryText( 266 *aux_text_.get(), 267 window_visible_ && aux_text_visible_); 268 } 269 } 270 271 void InputMethodEngineIBus::SetCandidateWindowAuxTextVisible(bool visible) { 272 aux_text_visible_ = visible; 273 if (active_) { 274 // Should not show auxiliary text if the whole window visibility is false. 275 GetCurrentService()->UpdateAuxiliaryText( 276 *aux_text_.get(), 277 window_visible_ && aux_text_visible_); 278 } 279 } 280 281 void InputMethodEngineIBus::SetCandidateWindowPosition( 282 CandidateWindowPosition position) { 283 table_->set_show_window_at_composition(position == WINDOW_POS_COMPOSITTION); 284 if (active_) 285 GetCurrentService()->UpdateLookupTable(*table_.get(), window_visible_); 286 } 287 288 bool InputMethodEngineIBus::SetCandidates( 289 int context_id, 290 const std::vector<Candidate>& candidates, 291 std::string* error) { 292 if (!active_) { 293 *error = kErrorNotActive; 294 return false; 295 } 296 if (context_id != context_id_ || context_id_ == -1) { 297 *error = kErrorWrongContext; 298 return false; 299 } 300 301 // TODO: Nested candidates 302 candidate_ids_.clear(); 303 candidate_indexes_.clear(); 304 table_->mutable_candidates()->clear(); 305 for (std::vector<Candidate>::const_iterator ix = candidates.begin(); 306 ix != candidates.end(); ++ix) { 307 IBusLookupTable::Entry entry; 308 entry.value = ix->value; 309 entry.label = ix->label; 310 entry.annotation = ix->annotation; 311 entry.description_title = ix->usage.title; 312 entry.description_body = ix->usage.body; 313 314 // Store a mapping from the user defined ID to the candidate index. 315 candidate_indexes_[ix->id] = candidate_ids_.size(); 316 candidate_ids_.push_back(ix->id); 317 318 table_->mutable_candidates()->push_back(entry); 319 } 320 GetCurrentService()->UpdateLookupTable(*table_.get(), window_visible_); 321 return true; 322 } 323 324 bool InputMethodEngineIBus::SetCursorPosition(int context_id, int candidate_id, 325 std::string* error) { 326 if (!active_) { 327 *error = kErrorNotActive; 328 return false; 329 } 330 if (context_id != context_id_ || context_id_ == -1) { 331 *error = kErrorWrongContext; 332 return false; 333 } 334 335 std::map<int, int>::const_iterator position = 336 candidate_indexes_.find(candidate_id); 337 if (position == candidate_indexes_.end()) { 338 *error = kCandidateNotFound; 339 return false; 340 } 341 342 table_->set_cursor_position(position->second); 343 GetCurrentService()->UpdateLookupTable(*table_.get(), window_visible_); 344 return true; 345 } 346 347 bool InputMethodEngineIBus::SetMenuItems(const std::vector<MenuItem>& items) { 348 if (!active_) 349 return false; 350 351 IBusPropertyList properties; 352 for (std::vector<MenuItem>::const_iterator item = items.begin(); 353 item != items.end(); ++item) { 354 IBusProperty* property = new IBusProperty(); 355 if (!MenuItemToProperty(*item, property)) { 356 delete property; 357 DVLOG(1) << "Bad menu item"; 358 return false; 359 } 360 properties.push_back(property); 361 } 362 GetCurrentService()->RegisterProperties(properties); 363 return true; 364 } 365 366 bool InputMethodEngineIBus::UpdateMenuItems( 367 const std::vector<MenuItem>& items) { 368 if (!active_) 369 return false; 370 371 IBusPropertyList properties; 372 for (std::vector<MenuItem>::const_iterator item = items.begin(); 373 item != items.end(); ++item) { 374 IBusProperty* property = new IBusProperty(); 375 if (!MenuItemToProperty(*item, property)) { 376 delete property; 377 DVLOG(1) << "Bad menu item"; 378 return false; 379 } 380 properties.push_back(property); 381 } 382 GetCurrentService()->RegisterProperties(properties); 383 return true; 384 } 385 386 bool InputMethodEngineIBus::IsActive() const { 387 return active_; 388 } 389 390 void InputMethodEngineIBus::KeyEventDone(input_method::KeyEventHandle* key_data, 391 bool handled) { 392 KeyEventDoneCallback* callback = 393 reinterpret_cast<KeyEventDoneCallback*>(key_data); 394 callback->Run(handled); 395 delete callback; 396 } 397 398 bool InputMethodEngineIBus::DeleteSurroundingText(int context_id, 399 int offset, 400 size_t number_of_chars, 401 std::string* error) { 402 if (!active_) { 403 *error = kErrorNotActive; 404 return false; 405 } 406 if (context_id != context_id_ || context_id_ == -1) { 407 *error = kErrorWrongContext; 408 return false; 409 } 410 411 if (offset < 0 && static_cast<size_t>(-1 * offset) != size_t(number_of_chars)) 412 return false; // Currently we can only support preceding text. 413 414 // TODO(nona): Return false if there is ongoing composition. 415 GetCurrentService()->DeleteSurroundingText(offset, number_of_chars); 416 return true; 417 } 418 419 void InputMethodEngineIBus::FocusIn() { 420 focused_ = true; 421 if (!active_) 422 return; 423 context_id_ = next_context_id_; 424 ++next_context_id_; 425 426 InputContext context; 427 context.id = context_id_; 428 // TODO: Other types 429 context.type = "text"; 430 431 observer_->OnFocus(context); 432 } 433 434 void InputMethodEngineIBus::FocusOut() { 435 focused_ = false; 436 if (!active_) 437 return; 438 int context_id = context_id_; 439 context_id_ = -1; 440 observer_->OnBlur(context_id); 441 } 442 443 void InputMethodEngineIBus::Enable() { 444 active_ = true; 445 observer_->OnActivate(engine_id_); 446 FocusIn(); 447 448 // Calls RequireSurroundingText once here to notify ibus-daemon to send 449 // surrounding text to this engine. 450 GetCurrentService()->RequireSurroundingText(); 451 } 452 453 void InputMethodEngineIBus::Disable() { 454 active_ = false; 455 observer_->OnDeactivated(engine_id_); 456 } 457 458 void InputMethodEngineIBus::PropertyActivate( 459 const std::string& property_name, 460 ibus::IBusPropertyState property_state) { 461 observer_->OnMenuItemActivated(engine_id_, property_name); 462 } 463 464 void InputMethodEngineIBus::PropertyShow( 465 const std::string& property_name) { 466 } 467 468 void InputMethodEngineIBus::PropertyHide( 469 const std::string& property_name) { 470 } 471 472 void InputMethodEngineIBus::SetCapability( 473 IBusCapability capability) { 474 } 475 476 void InputMethodEngineIBus::Reset() { 477 observer_->OnReset(engine_id_); 478 } 479 480 void InputMethodEngineIBus::ProcessKeyEvent( 481 uint32 keysym, 482 uint32 keycode, 483 uint32 state, 484 const KeyEventDoneCallback& callback) { 485 486 KeyEventDoneCallback *handler = new KeyEventDoneCallback(); 487 *handler = callback; 488 489 KeyboardEvent event; 490 event.type = !(state & kIBusKeyReleaseMask) ? "keydown" : "keyup"; 491 event.key = input_method::GetIBusKey(keysym); 492 event.code = input_method::GetIBusKeyCode(keycode); 493 event.alt_key = state & kIBusAltKeyMask; 494 event.ctrl_key = state & kIBusCtrlKeyMask; 495 event.shift_key = state & kIBusShiftKeyMask; 496 event.caps_lock = state & kIBusCapsLockMask; 497 observer_->OnKeyEvent( 498 engine_id_, 499 event, 500 reinterpret_cast<input_method::KeyEventHandle*>(handler)); 501 } 502 503 void InputMethodEngineIBus::CandidateClicked(uint32 index, 504 ibus::IBusMouseButton button, 505 uint32 state) { 506 if (index > candidate_ids_.size()) { 507 return; 508 } 509 510 MouseButtonEvent pressed_button; 511 switch (button) { 512 case ibus::IBUS_MOUSE_BUTTON_LEFT: 513 pressed_button = MOUSE_BUTTON_LEFT; 514 break; 515 case ibus::IBUS_MOUSE_BUTTON_MIDDLE: 516 pressed_button = MOUSE_BUTTON_MIDDLE; 517 break; 518 case ibus::IBUS_MOUSE_BUTTON_RIGHT: 519 pressed_button = MOUSE_BUTTON_RIGHT; 520 break; 521 default: 522 DVLOG(1) << "Unknown button: " << button; 523 pressed_button = MOUSE_BUTTON_LEFT; 524 break; 525 } 526 527 observer_->OnCandidateClicked( 528 engine_id_, candidate_ids_.at(index), pressed_button); 529 } 530 531 void InputMethodEngineIBus::SetSurroundingText(const std::string& text, 532 uint32 cursor_pos, 533 uint32 anchor_pos) { 534 observer_->OnSurroundingTextChanged(engine_id_, 535 text, 536 static_cast<int>(cursor_pos), 537 static_cast<int>(anchor_pos)); 538 } 539 540 IBusEngineService* InputMethodEngineIBus::GetCurrentService() { 541 return DBusThreadManager::Get()->GetIBusEngineService(object_path_); 542 } 543 544 bool InputMethodEngineIBus::MenuItemToProperty( 545 const MenuItem& item, 546 IBusProperty* property) { 547 property->set_key(item.id); 548 549 if (item.modified & MENU_ITEM_MODIFIED_LABEL) { 550 property->set_label(item.label); 551 } 552 if (item.modified & MENU_ITEM_MODIFIED_VISIBLE) { 553 property->set_visible(item.visible); 554 } 555 if (item.modified & MENU_ITEM_MODIFIED_CHECKED) { 556 property->set_checked(item.checked); 557 } 558 if (item.modified & MENU_ITEM_MODIFIED_ENABLED) { 559 // TODO(nona): implement sensitive entry(crbug.com/140192). 560 } 561 if (item.modified & MENU_ITEM_MODIFIED_STYLE) { 562 IBusProperty::IBusPropertyType type = 563 IBusProperty::IBUS_PROPERTY_TYPE_NORMAL; 564 if (!item.children.empty()) { 565 type = IBusProperty::IBUS_PROPERTY_TYPE_MENU; 566 } else { 567 switch (item.style) { 568 case MENU_ITEM_STYLE_NONE: 569 type = IBusProperty::IBUS_PROPERTY_TYPE_NORMAL; 570 break; 571 case MENU_ITEM_STYLE_CHECK: 572 type = IBusProperty::IBUS_PROPERTY_TYPE_TOGGLE; 573 break; 574 case MENU_ITEM_STYLE_RADIO: 575 type = IBusProperty::IBUS_PROPERTY_TYPE_RADIO; 576 break; 577 case MENU_ITEM_STYLE_SEPARATOR: 578 type = IBusProperty::IBUS_PROPERTY_TYPE_SEPARATOR; 579 break; 580 } 581 } 582 property->set_type(type); 583 } 584 585 for (std::vector<MenuItem>::const_iterator child = item.children.begin(); 586 child != item.children.end(); ++child) { 587 IBusProperty* new_property = new IBusProperty(); 588 if (!MenuItemToProperty(*child, new_property)) { 589 delete new_property; 590 DVLOG(1) << "Bad menu item child"; 591 return false; 592 } 593 property->mutable_sub_properties()->push_back(new_property); 594 } 595 596 return true; 597 } 598 599 void InputMethodEngineIBus::OnConnected() { 600 RegisterComponent(); 601 } 602 603 void InputMethodEngineIBus::OnDisconnected() { 604 } 605 606 bool InputMethodEngineIBus::IsConnected() { 607 return DBusThreadManager::Get()->GetIBusClient() != NULL; 608 } 609 610 void InputMethodEngineIBus::RegisterComponent() { 611 chromeos::IBusClient* client = 612 chromeos::DBusThreadManager::Get()->GetIBusClient(); 613 client->RegisterComponent( 614 *component_.get(), 615 base::Bind(&InputMethodEngineIBus::OnComponentRegistered, 616 weak_ptr_factory_.GetWeakPtr()), 617 base::Bind(&InputMethodEngineIBus::OnComponentRegistrationFailed, 618 weak_ptr_factory_.GetWeakPtr())); 619 } 620 621 void InputMethodEngineIBus::OnComponentRegistered() { 622 DBusThreadManager::Get()->GetIBusEngineFactoryService()-> 623 SetCreateEngineHandler(ibus_id_, 624 base::Bind( 625 &InputMethodEngineIBus::CreateEngineHandler, 626 weak_ptr_factory_.GetWeakPtr())); 627 } 628 629 void InputMethodEngineIBus::OnComponentRegistrationFailed() { 630 DVLOG(1) << "Failed to register input method components."; 631 // TODO(nona): Implement error handling. 632 } 633 634 void InputMethodEngineIBus::CreateEngineHandler( 635 const IBusEngineFactoryService::CreateEngineResponseSender& sender) { 636 GetCurrentService()->UnsetEngine(this); 637 DBusThreadManager::Get()->RemoveIBusEngineService(object_path_); 638 639 object_path_ = DBusThreadManager::Get()->GetIBusEngineFactoryService()-> 640 GenerateUniqueObjectPath(); 641 642 GetCurrentService()->SetEngine(this); 643 sender.Run(object_path_); 644 } 645 646 } // namespace chromeos 647