input_method_manager_impl.cc revision a93a17c8d99d686bd4a1511e5504e5e6cc9fcadf
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_manager_impl.h" 6 7#include <algorithm> // std::find 8 9#include "base/basictypes.h" 10#include "base/bind.h" 11#include "base/location.h" 12#include "base/memory/scoped_ptr.h" 13#include "base/string_util.h" 14#include "base/stringprintf.h" 15#include "chrome/browser/chromeos/input_method/candidate_window_controller.h" 16#include "chrome/browser/chromeos/input_method/component_extension_ime_manager_impl.h" 17#include "chrome/browser/chromeos/input_method/input_method_engine_ibus.h" 18#include "chrome/browser/chromeos/language_preferences.h" 19#include "chromeos/dbus/dbus_thread_manager.h" 20#include "chromeos/dbus/ibus/ibus_client.h" 21#include "chromeos/dbus/ibus/ibus_input_context_client.h" 22#include "chromeos/ime/component_extension_ime_manager.h" 23#include "chromeos/ime/extension_ime_util.h" 24#include "chromeos/ime/input_method_delegate.h" 25#include "chromeos/ime/xkeyboard.h" 26#include "third_party/icu/public/common/unicode/uloc.h" 27#include "ui/base/accelerators/accelerator.h" 28 29namespace chromeos { 30namespace input_method { 31 32namespace { 33 34const char nacl_mozc_us_id[] = 35 "_comp_ime_fpfbhcjppmaeaijcidgiibchfbnhbeljnacl_mozc_us"; 36const char nacl_mozc_jp_id[] = 37 "_comp_ime_fpfbhcjppmaeaijcidgiibchfbnhbeljnacl_mozc_jp"; 38 39bool Contains(const std::vector<std::string>& container, 40 const std::string& value) { 41 return std::find(container.begin(), container.end(), value) != 42 container.end(); 43} 44 45const struct MigrationInputMethodList { 46 const char* old_input_method; 47 const char* new_input_method; 48} kMigrationInputMethodList[] = { 49 { "mozc", "_comp_ime_fpfbhcjppmaeaijcidgiibchfbnhbeljnacl_mozc_us" }, 50 { "mozc-jp", "_comp_ime_fpfbhcjppmaeaijcidgiibchfbnhbeljnacl_mozc_jp" }, 51 { "mozc-dv", "_comp_ime_fpfbhcjppmaeaijcidgiibchfbnhbeljnacl_mozc_us" }, 52}; 53 54} // namespace 55 56InputMethodManagerImpl::InputMethodManagerImpl( 57 scoped_ptr<InputMethodDelegate> delegate) 58 : delegate_(delegate.Pass()), 59 state_(STATE_LOGIN_SCREEN), 60 util_(delegate_.get(), GetSupportedInputMethods()), 61 component_extension_ime_manager_(new ComponentExtensionIMEManager()), 62 weak_ptr_factory_(this) { 63 IBusDaemonController::GetInstance()->AddObserver(this); 64} 65 66InputMethodManagerImpl::~InputMethodManagerImpl() { 67 if (ibus_controller_.get()) 68 ibus_controller_->RemoveObserver(this); 69 IBusDaemonController::GetInstance()->RemoveObserver(this); 70 if (candidate_window_controller_.get()) { 71 candidate_window_controller_->RemoveObserver(this); 72 candidate_window_controller_->Shutdown(); 73 } 74} 75 76void InputMethodManagerImpl::AddObserver( 77 InputMethodManager::Observer* observer) { 78 observers_.AddObserver(observer); 79} 80 81void InputMethodManagerImpl::AddCandidateWindowObserver( 82 InputMethodManager::CandidateWindowObserver* observer) { 83 candidate_window_observers_.AddObserver(observer); 84} 85 86void InputMethodManagerImpl::RemoveObserver( 87 InputMethodManager::Observer* observer) { 88 observers_.RemoveObserver(observer); 89} 90 91void InputMethodManagerImpl::RemoveCandidateWindowObserver( 92 InputMethodManager::CandidateWindowObserver* observer) { 93 candidate_window_observers_.RemoveObserver(observer); 94} 95 96void InputMethodManagerImpl::SetState(State new_state) { 97 const State old_state = state_; 98 state_ = new_state; 99 switch (state_) { 100 case STATE_LOGIN_SCREEN: 101 break; 102 case STATE_BROWSER_SCREEN: 103 if (old_state == STATE_LOCK_SCREEN) 104 OnScreenUnlocked(); 105 break; 106 case STATE_LOCK_SCREEN: 107 OnScreenLocked(); 108 break; 109 case STATE_TERMINATING: { 110 if (candidate_window_controller_.get()) { 111 candidate_window_controller_->Shutdown(); 112 candidate_window_controller_.reset(); 113 } 114 break; 115 } 116 } 117} 118 119scoped_ptr<InputMethodDescriptors> 120InputMethodManagerImpl::GetSupportedInputMethods() const { 121 return whitelist_.GetSupportedInputMethods(); 122} 123 124scoped_ptr<InputMethodDescriptors> 125InputMethodManagerImpl::GetActiveInputMethods() const { 126 scoped_ptr<InputMethodDescriptors> result(new InputMethodDescriptors); 127 // Build the active input method descriptors from the active input 128 // methods cache |active_input_method_ids_|. 129 for (size_t i = 0; i < active_input_method_ids_.size(); ++i) { 130 const std::string& input_method_id = active_input_method_ids_[i]; 131 const InputMethodDescriptor* descriptor = 132 util_.GetInputMethodDescriptorFromId(input_method_id); 133 if (descriptor) { 134 result->push_back(*descriptor); 135 } else { 136 std::map<std::string, InputMethodDescriptor>::const_iterator ix = 137 extra_input_methods_.find(input_method_id); 138 if (ix != extra_input_methods_.end()) 139 result->push_back(ix->second); 140 else 141 DVLOG(1) << "Descriptor is not found for: " << input_method_id; 142 } 143 } 144 if (result->empty()) { 145 // Initially |active_input_method_ids_| is empty. browser_tests might take 146 // this path. 147 result->push_back( 148 InputMethodUtil::GetFallbackInputMethodDescriptor()); 149 } 150 return result.Pass(); 151} 152 153size_t InputMethodManagerImpl::GetNumActiveInputMethods() const { 154 return active_input_method_ids_.size(); 155} 156 157void InputMethodManagerImpl::EnableLayouts(const std::string& language_code, 158 const std::string& initial_layout) { 159 if (state_ == STATE_TERMINATING) 160 return; 161 162 std::vector<std::string> candidates; 163 // Add input methods associated with the language. 164 util_.GetInputMethodIdsFromLanguageCode(language_code, 165 kKeyboardLayoutsOnly, 166 &candidates); 167 // Add the hardware keyboard as well. We should always add this so users 168 // can use the hardware keyboard on the login screen and the screen locker. 169 candidates.push_back(util_.GetHardwareInputMethodId()); 170 171 std::vector<std::string> layouts; 172 // First, add the initial input method ID, if it's requested, to 173 // layouts, so it appears first on the list of active input 174 // methods at the input language status menu. 175 if (util_.IsValidInputMethodId(initial_layout) && 176 InputMethodUtil::IsKeyboardLayout(initial_layout)) { 177 layouts.push_back(initial_layout); 178 } else if (!initial_layout.empty()) { 179 DVLOG(1) << "EnableLayouts: ignoring non-keyboard or invalid ID: " 180 << initial_layout; 181 } 182 183 // Add candidates to layouts, while skipping duplicates. 184 for (size_t i = 0; i < candidates.size(); ++i) { 185 const std::string& candidate = candidates[i]; 186 // Not efficient, but should be fine, as the two vectors are very 187 // short (2-5 items). 188 if (!Contains(layouts, candidate)) 189 layouts.push_back(candidate); 190 } 191 192 active_input_method_ids_.swap(layouts); 193 ChangeInputMethod(initial_layout); // you can pass empty |initial_layout|. 194} 195 196bool InputMethodManagerImpl::EnableInputMethods( 197 const std::vector<std::string>& new_active_input_method_ids) { 198 if (state_ == STATE_TERMINATING) 199 return false; 200 201 // Filter unknown or obsolete IDs. 202 std::vector<std::string> new_active_input_method_ids_filtered; 203 204 for (size_t i = 0; i < new_active_input_method_ids.size(); ++i) { 205 const std::string& input_method_id = new_active_input_method_ids[i]; 206 if (util_.IsValidInputMethodId(input_method_id)) 207 new_active_input_method_ids_filtered.push_back(input_method_id); 208 else 209 DVLOG(1) << "EnableInputMethods: Invalid ID: " << input_method_id; 210 } 211 212 if (new_active_input_method_ids_filtered.empty()) { 213 DVLOG(1) << "EnableInputMethods: No valid input method ID"; 214 return false; 215 } 216 217 // Copy extension IDs to |new_active_input_method_ids_filtered|. We have to 218 // keep relative order of the extension input method IDs. 219 for (size_t i = 0; i < active_input_method_ids_.size(); ++i) { 220 const std::string& input_method_id = active_input_method_ids_[i]; 221 if (extension_ime_util::IsExtensionIME(input_method_id)) 222 new_active_input_method_ids_filtered.push_back(input_method_id); 223 } 224 active_input_method_ids_.swap(new_active_input_method_ids_filtered); 225 226 if (component_extension_ime_manager_->IsInitialized()) 227 LoadNecessaryComponentExtensions(); 228 229 if (ContainOnlyKeyboardLayout(active_input_method_ids_)) { 230 // Do NOT call ibus_controller_->Stop(); here to work around a crash issue 231 // at crosbug.com/27051. 232 // TODO(yusukes): We can safely call Stop(); here once crosbug.com/26443 233 // is implemented. 234 } else { 235 MaybeInitializeCandidateWindowController(); 236 IBusDaemonController::GetInstance()->Start(); 237 } 238 239 // If |current_input_method| is no longer in |active_input_method_ids_|, 240 // ChangeInputMethod() picks the first one in |active_input_method_ids_|. 241 ChangeInputMethod(current_input_method_.id()); 242 return true; 243} 244 245bool InputMethodManagerImpl::MigrateOldInputMethods( 246 std::vector<std::string>* input_method_ids) { 247 bool rewritten = false; 248 for (size_t i = 0; i < input_method_ids->size(); ++i) { 249 for (size_t j = 0; j < ARRAYSIZE_UNSAFE(kMigrationInputMethodList); ++j) { 250 if (input_method_ids->at(i) == 251 kMigrationInputMethodList[j].old_input_method) { 252 input_method_ids->at(i).assign( 253 kMigrationInputMethodList[j].new_input_method); 254 rewritten = true; 255 } 256 } 257 } 258 return rewritten; 259} 260 261bool InputMethodManagerImpl::SetInputMethodConfig( 262 const std::string& section, 263 const std::string& config_name, 264 const InputMethodConfigValue& value) { 265 DCHECK(section != language_prefs::kGeneralSectionName || 266 config_name != language_prefs::kPreloadEnginesConfigName); 267 268 if (state_ == STATE_TERMINATING) 269 return false; 270 return ibus_controller_->SetInputMethodConfig(section, config_name, value); 271} 272 273void InputMethodManagerImpl::ChangeInputMethod( 274 const std::string& input_method_id) { 275 ChangeInputMethodInternal(input_method_id, false); 276} 277 278bool InputMethodManagerImpl::ChangeInputMethodInternal( 279 const std::string& input_method_id, 280 bool show_message) { 281 if (state_ == STATE_TERMINATING) 282 return false; 283 284 if (!component_extension_ime_manager_->IsInitialized() || 285 (!InputMethodUtil::IsKeyboardLayout(input_method_id) && 286 !IsIBusConnectionAlive())) { 287 // We can't change input method before the initialization of component 288 // extension ime manager or before connection to ibus-daemon is not 289 // established. ChangeInputMethod will be called with 290 // |pending_input_method_| when the both initialization is done. 291 pending_input_method_ = input_method_id; 292 return false; 293 } 294 295 std::string input_method_id_to_switch = input_method_id; 296 297 // Sanity check. 298 if (!InputMethodIsActivated(input_method_id)) { 299 scoped_ptr<InputMethodDescriptors> input_methods(GetActiveInputMethods()); 300 DCHECK(!input_methods->empty()); 301 input_method_id_to_switch = input_methods->at(0).id(); 302 if (!input_method_id.empty()) { 303 DVLOG(1) << "Can't change the current input method to " 304 << input_method_id << " since the engine is not enabled. " 305 << "Switch to " << input_method_id_to_switch << " instead."; 306 } 307 } 308 309 IBusInputContextClient* input_context = 310 chromeos::DBusThreadManager::Get()->GetIBusInputContextClient(); 311 const std::string current_input_method_id = current_input_method_.id(); 312 IBusClient* client = DBusThreadManager::Get()->GetIBusClient(); 313 if (InputMethodUtil::IsKeyboardLayout(input_method_id_to_switch)) { 314 FOR_EACH_OBSERVER(InputMethodManager::Observer, 315 observers_, 316 InputMethodPropertyChanged(this)); 317 // Hack for fixing http://crosbug.com/p/12798 318 // We should notify IME switching to ibus-daemon, otherwise 319 // IBusPreeditFocusMode does not work. To achieve it, change engine to 320 // itself if the next engine is XKB layout. 321 if (current_input_method_id.empty() || 322 InputMethodUtil::IsKeyboardLayout(current_input_method_id)) { 323 if (input_context) 324 input_context->Reset(); 325 } else { 326 if (client) 327 client->SetGlobalEngine(current_input_method_id, 328 base::Bind(&base::DoNothing)); 329 } 330 if (input_context) 331 input_context->SetIsXKBLayout(true); 332 } else { 333 DCHECK(client); 334 client->SetGlobalEngine(input_method_id_to_switch, 335 base::Bind(&base::DoNothing)); 336 if (input_context) 337 input_context->SetIsXKBLayout(false); 338 } 339 340 if (current_input_method_id != input_method_id_to_switch) { 341 // Clear input method properties unconditionally if 342 // |input_method_id_to_switch| is not equal to |current_input_method_id|. 343 // 344 // When switching to another input method and no text area is focused, 345 // RegisterProperties signal for the new input method will NOT be sent 346 // until a text area is focused. Therefore, we have to clear the old input 347 // method properties here to keep the input method switcher status 348 // consistent. 349 // 350 // When |input_method_id_to_switch| and |current_input_method_id| are the 351 // same, the properties shouldn't be cleared. If we do that, something 352 // wrong happens in step #4 below: 353 // 1. Enable "xkb:us::eng" and "mozc". Switch to "mozc". 354 // 2. Focus Omnibox. IME properties for mozc are sent to Chrome. 355 // 3. Switch to "xkb:us::eng". No function in this file is called. 356 // 4. Switch back to "mozc". ChangeInputMethod("mozc") is called, but it's 357 // basically NOP since ibus-daemon's current IME is already "mozc". 358 // IME properties are not sent to Chrome for the same reason. 359 // TODO(nona): Revisit above comment once ibus-daemon is gone. 360 ibus_controller_->ClearProperties(); 361 362 const InputMethodDescriptor* descriptor = NULL; 363 if (!extension_ime_util::IsExtensionIME(input_method_id_to_switch)) { 364 descriptor = 365 util_.GetInputMethodDescriptorFromId(input_method_id_to_switch); 366 } else { 367 std::map<std::string, InputMethodDescriptor>::const_iterator i = 368 extra_input_methods_.find(input_method_id_to_switch); 369 DCHECK(i != extra_input_methods_.end()); 370 descriptor = &(i->second); 371 } 372 DCHECK(descriptor); 373 374 previous_input_method_ = current_input_method_; 375 current_input_method_ = *descriptor; 376 } 377 378 // Change the keyboard layout to a preferred layout for the input method. 379 if (!xkeyboard_->SetCurrentKeyboardLayoutByName( 380 current_input_method_.GetPreferredKeyboardLayout())) { 381 LOG(ERROR) << "Failed to change keyboard layout to " 382 << current_input_method_.GetPreferredKeyboardLayout(); 383 } 384 385 // Update input method indicators (e.g. "US", "DV") in Chrome windows. 386 FOR_EACH_OBSERVER(InputMethodManager::Observer, 387 observers_, 388 InputMethodChanged(this, show_message)); 389 return true; 390} 391 392void InputMethodManagerImpl::OnComponentExtensionInitialized( 393 scoped_ptr<ComponentExtensionIMEManagerDelegate> delegate) { 394 DCHECK(thread_checker_.CalledOnValidThread()); 395 component_extension_ime_manager_->Initialize(delegate.Pass()); 396 util_.SetComponentExtensions( 397 component_extension_ime_manager_->GetAllIMEAsInputMethodDescriptor()); 398 399 LoadNecessaryComponentExtensions(); 400 401 if (!pending_input_method_.empty()) { 402 if (ChangeInputMethodInternal(pending_input_method_, false)) 403 pending_input_method_.clear(); 404 } 405 406} 407 408void InputMethodManagerImpl::LoadNecessaryComponentExtensions() { 409 if (!component_extension_ime_manager_->IsInitialized()) 410 return; 411 // Load component extensions but also update |active_input_method_ids_| as 412 // some component extension IMEs may have been removed from the Chrome OS 413 // image. If specified component extension IME no longer exists, falling back 414 // to an existing IME. 415 std::vector<std::string> unfiltered_input_method_ids = 416 active_input_method_ids_; 417 active_input_method_ids_.clear(); 418 for (size_t i = 0; i < unfiltered_input_method_ids.size(); ++i) { 419 if (!component_extension_ime_manager_->IsComponentExtensionIMEId( 420 unfiltered_input_method_ids[i])) { 421 // Legacy IMEs or xkb layouts are alwayes active. 422 active_input_method_ids_.push_back(unfiltered_input_method_ids[i]); 423 } else if (component_extension_ime_manager_->IsWhitelisted( 424 unfiltered_input_method_ids[i])) { 425 component_extension_ime_manager_->LoadComponentExtensionIME( 426 unfiltered_input_method_ids[i]); 427 active_input_method_ids_.push_back(unfiltered_input_method_ids[i]); 428 } 429 } 430} 431 432void InputMethodManagerImpl::ActivateInputMethodProperty( 433 const std::string& key) { 434 DCHECK(!key.empty()); 435 ibus_controller_->ActivateInputMethodProperty(key); 436} 437 438void InputMethodManagerImpl::AddInputMethodExtension( 439 const std::string& id, 440 const std::string& name, 441 const std::vector<std::string>& layouts, 442 const std::string& language, 443 InputMethodEngine* engine) { 444 if (state_ == STATE_TERMINATING) 445 return; 446 447 if (!extension_ime_util::IsExtensionIME(id) && 448 !ComponentExtensionIMEManager::IsComponentExtensionIMEId(id)) { 449 DVLOG(1) << id << " is not a valid extension input method ID."; 450 return; 451 } 452 453 // TODO(nona): Support options page for normal extension ime. 454 // crbug.com/156283. 455 extra_input_methods_[id] = 456 InputMethodDescriptor(id, name, layouts, language, GURL()); 457 if (Contains(enabled_extension_imes_, id) && 458 !ComponentExtensionIMEManager::IsComponentExtensionIMEId(id)) { 459 if (!Contains(active_input_method_ids_, id)) { 460 active_input_method_ids_.push_back(id); 461 } else { 462 DVLOG(1) << "AddInputMethodExtension: alread added: " 463 << id << ", " << name; 464 // Call Start() anyway, just in case. 465 } 466 467 // Ensure that the input method daemon is running. 468 MaybeInitializeCandidateWindowController(); 469 IBusDaemonController::GetInstance()->Start(); 470 } 471 472 extra_input_method_instances_[id] = 473 static_cast<InputMethodEngineIBus*>(engine); 474} 475 476void InputMethodManagerImpl::RemoveInputMethodExtension(const std::string& id) { 477 if (!extension_ime_util::IsExtensionIME(id)) 478 DVLOG(1) << id << " is not a valid extension input method ID."; 479 480 std::vector<std::string>::iterator i = std::find( 481 active_input_method_ids_.begin(), active_input_method_ids_.end(), id); 482 if (i != active_input_method_ids_.end()) 483 active_input_method_ids_.erase(i); 484 extra_input_methods_.erase(id); 485 486 if (ContainOnlyKeyboardLayout(active_input_method_ids_)) { 487 // Do NOT call ibus_controller_->Stop(); here to work around a crash issue 488 // at crosbug.com/27051. 489 // TODO(yusukes): We can safely call Stop(); here once crosbug.com/26443 490 // is implemented. 491 } 492 493 // If |current_input_method| is no longer in |active_input_method_ids_|, 494 // switch to the first one in |active_input_method_ids_|. 495 ChangeInputMethod(current_input_method_.id()); 496 497 std::map<std::string, InputMethodEngineIBus*>::iterator ite = 498 extra_input_method_instances_.find(id); 499 if (ite == extra_input_method_instances_.end()) { 500 DVLOG(1) << "The engine instance of " << id << " has already gone."; 501 } else { 502 // Do NOT release the actual instance here. This class does not take an 503 // onwership of engine instance. 504 extra_input_method_instances_.erase(ite); 505 } 506} 507 508void InputMethodManagerImpl::GetInputMethodExtensions( 509 InputMethodDescriptors* result) { 510 // Build the extension input method descriptors from the extra input 511 // methods cache |extra_input_methods_|. 512 std::map<std::string, InputMethodDescriptor>::iterator iter; 513 for (iter = extra_input_methods_.begin(); iter != extra_input_methods_.end(); 514 ++iter) { 515 if (extension_ime_util::IsExtensionIME(iter->first)) 516 result->push_back(iter->second); 517 } 518} 519 520void InputMethodManagerImpl::SetEnabledExtensionImes( 521 std::vector<std::string>* ids) { 522 enabled_extension_imes_.clear(); 523 enabled_extension_imes_.insert(enabled_extension_imes_.end(), 524 ids->begin(), 525 ids->end()); 526 527 bool active_imes_changed = false; 528 529 for (std::map<std::string, InputMethodDescriptor>::iterator extra_iter = 530 extra_input_methods_.begin(); extra_iter != extra_input_methods_.end(); 531 ++extra_iter) { 532 if (ComponentExtensionIMEManager::IsComponentExtensionIMEId( 533 extra_iter->first)) 534 continue; // Do not filter component extension. 535 std::vector<std::string>::iterator active_iter = std::find( 536 active_input_method_ids_.begin(), active_input_method_ids_.end(), 537 extra_iter->first); 538 539 bool active = active_iter != active_input_method_ids_.end(); 540 bool enabled = Contains(enabled_extension_imes_, extra_iter->first); 541 542 if (active && !enabled) 543 active_input_method_ids_.erase(active_iter); 544 545 if (!active && enabled) 546 active_input_method_ids_.push_back(extra_iter->first); 547 548 if (active == !enabled) 549 active_imes_changed = true; 550 } 551 552 if (active_imes_changed) { 553 MaybeInitializeCandidateWindowController(); 554 IBusDaemonController::GetInstance()->Start(); 555 556 // If |current_input_method| is no longer in |active_input_method_ids_|, 557 // switch to the first one in |active_input_method_ids_|. 558 ChangeInputMethod(current_input_method_.id()); 559 } 560} 561 562bool InputMethodManagerImpl::SwitchToNextInputMethod() { 563 // Sanity checks. 564 if (active_input_method_ids_.empty()) { 565 DVLOG(1) << "active input method is empty"; 566 return false; 567 } 568 if (current_input_method_.id().empty()) { 569 DVLOG(1) << "current_input_method_ is unknown"; 570 return false; 571 } 572 573 // Do not consume key event if there is only one input method is enabled. 574 // Ctrl+Space or Alt+Shift may be used by other application. 575 if (active_input_method_ids_.size() == 1) 576 return false; 577 578 // Find the next input method and switch to it. 579 SwitchToNextInputMethodInternal(active_input_method_ids_, 580 current_input_method_.id()); 581 return true; 582} 583 584bool InputMethodManagerImpl::SwitchToPreviousInputMethod() { 585 // Sanity check. 586 if (active_input_method_ids_.empty()) { 587 DVLOG(1) << "active input method is empty"; 588 return false; 589 } 590 591 // Do not consume key event if there is only one input method is enabled. 592 // Ctrl+Space or Alt+Shift may be used by other application. 593 if (active_input_method_ids_.size() == 1) 594 return false; 595 596 if (previous_input_method_.id().empty() || 597 previous_input_method_.id() == current_input_method_.id()) { 598 return SwitchToNextInputMethod(); 599 } 600 601 std::vector<std::string>::const_iterator iter = 602 std::find(active_input_method_ids_.begin(), 603 active_input_method_ids_.end(), 604 previous_input_method_.id()); 605 if (iter == active_input_method_ids_.end()) { 606 // previous_input_method_ is not supported. 607 return SwitchToNextInputMethod(); 608 } 609 ChangeInputMethodInternal(*iter, true); 610 return true; 611} 612 613bool InputMethodManagerImpl::SwitchInputMethod( 614 const ui::Accelerator& accelerator) { 615 // Sanity check. 616 if (active_input_method_ids_.empty()) { 617 DVLOG(1) << "active input method is empty"; 618 return false; 619 } 620 621 // Get the list of input method ids for the |accelerator|. For example, get 622 // { "mozc-hangul", "xkb:kr:kr104:kor" } for ui::VKEY_DBE_SBCSCHAR. 623 std::vector<std::string> input_method_ids_to_switch; 624 switch (accelerator.key_code()) { 625 case ui::VKEY_CONVERT: // Henkan key on JP106 keyboard 626 input_method_ids_to_switch.push_back(nacl_mozc_jp_id); 627 break; 628 case ui::VKEY_NONCONVERT: // Muhenkan key on JP106 keyboard 629 input_method_ids_to_switch.push_back("xkb:jp::jpn"); 630 break; 631 case ui::VKEY_DBE_SBCSCHAR: // ZenkakuHankaku key on JP106 keyboard 632 case ui::VKEY_DBE_DBCSCHAR: 633 input_method_ids_to_switch.push_back(nacl_mozc_jp_id); 634 input_method_ids_to_switch.push_back("xkb:jp::jpn"); 635 break; 636 case ui::VKEY_HANGUL: // Hangul (or right Alt) key on Korean keyboard 637 input_method_ids_to_switch.push_back("mozc-hangul"); 638 input_method_ids_to_switch.push_back("xkb:kr:kr104:kor"); 639 break; 640 default: 641 NOTREACHED(); 642 break; 643 } 644 if (input_method_ids_to_switch.empty()) { 645 DVLOG(1) << "Unexpected VKEY: " << accelerator.key_code(); 646 return false; 647 } 648 649 // Obtain the intersection of input_method_ids_to_switch and 650 // active_input_method_ids_. The order of IDs in active_input_method_ids_ is 651 // preserved. 652 std::vector<std::string> ids; 653 for (size_t i = 0; i < input_method_ids_to_switch.size(); ++i) { 654 const std::string& id = input_method_ids_to_switch[i]; 655 if (Contains(active_input_method_ids_, id)) 656 ids.push_back(id); 657 } 658 if (ids.empty()) { 659 // No input method for the accelerator is active. For example, we should 660 // just ignore VKEY_HANGUL when mozc-hangul is not active. 661 return false; 662 } 663 664 SwitchToNextInputMethodInternal(ids, current_input_method_.id()); 665 return true; // consume the accelerator. 666} 667 668void InputMethodManagerImpl::SwitchToNextInputMethodInternal( 669 const std::vector<std::string>& input_method_ids, 670 const std::string& current_input_method_id) { 671 std::vector<std::string>::const_iterator iter = 672 std::find(input_method_ids.begin(), 673 input_method_ids.end(), 674 current_input_method_id); 675 if (iter != input_method_ids.end()) 676 ++iter; 677 if (iter == input_method_ids.end()) 678 iter = input_method_ids.begin(); 679 ChangeInputMethodInternal(*iter, true); 680} 681 682InputMethodDescriptor InputMethodManagerImpl::GetCurrentInputMethod() const { 683 if (current_input_method_.id().empty()) 684 return InputMethodUtil::GetFallbackInputMethodDescriptor(); 685 return current_input_method_; 686} 687 688InputMethodPropertyList 689InputMethodManagerImpl::GetCurrentInputMethodProperties() const { 690 // This check is necessary since an IME property (e.g. for Pinyin) might be 691 // sent from ibus-daemon AFTER the current input method is switched to XKB. 692 if (InputMethodUtil::IsKeyboardLayout(GetCurrentInputMethod().id())) 693 return InputMethodPropertyList(); 694 return ibus_controller_->GetCurrentProperties(); 695} 696 697XKeyboard* InputMethodManagerImpl::GetXKeyboard() { 698 return xkeyboard_.get(); 699} 700 701InputMethodUtil* InputMethodManagerImpl::GetInputMethodUtil() { 702 return &util_; 703} 704 705ComponentExtensionIMEManager* 706 InputMethodManagerImpl::GetComponentExtensionIMEManager() { 707 DCHECK(thread_checker_.CalledOnValidThread()); 708 return component_extension_ime_manager_.get(); 709} 710 711void InputMethodManagerImpl::OnConnected() { 712 for (std::map<std::string, InputMethodEngineIBus*>::iterator ite = 713 extra_input_method_instances_.begin(); 714 ite != extra_input_method_instances_.end(); 715 ite++) { 716 if (Contains(enabled_extension_imes_, ite->first) || 717 (component_extension_ime_manager_->IsInitialized() && 718 component_extension_ime_manager_->IsWhitelisted(ite->first))) { 719 ite->second->OnConnected(); 720 } 721 } 722 723 if (!pending_input_method_.empty()) { 724 if (ChangeInputMethodInternal(pending_input_method_, false)) 725 pending_input_method_.clear(); 726 } 727} 728 729void InputMethodManagerImpl::OnDisconnected() { 730 for (std::map<std::string, InputMethodEngineIBus*>::iterator ite = 731 extra_input_method_instances_.begin(); 732 ite != extra_input_method_instances_.end(); 733 ite++) { 734 if (Contains(enabled_extension_imes_, ite->first)) 735 ite->second->OnDisconnected(); 736 } 737} 738 739void InputMethodManagerImpl::InitializeComponentExtension() { 740 ComponentExtensionIMEManagerImpl* impl = 741 new ComponentExtensionIMEManagerImpl(); 742 scoped_ptr<ComponentExtensionIMEManagerDelegate> delegate(impl); 743 impl->InitializeAsync(base::Bind( 744 &InputMethodManagerImpl::OnComponentExtensionInitialized, 745 weak_ptr_factory_.GetWeakPtr(), 746 base::Passed(&delegate))); 747} 748 749void InputMethodManagerImpl::Init(base::SequencedTaskRunner* ui_task_runner) { 750 DCHECK(!ibus_controller_.get()); 751 DCHECK(thread_checker_.CalledOnValidThread()); 752 753 ibus_controller_.reset(IBusController::Create()); 754 xkeyboard_.reset(XKeyboard::Create()); 755 ibus_controller_->AddObserver(this); 756 757 // We can't call impl->Initialize here, because file thread is not available 758 // at this moment. 759 ui_task_runner->PostTask( 760 FROM_HERE, 761 base::Bind(&InputMethodManagerImpl::InitializeComponentExtension, 762 weak_ptr_factory_.GetWeakPtr())); 763} 764 765void InputMethodManagerImpl::SetIBusControllerForTesting( 766 IBusController* ibus_controller) { 767 ibus_controller_.reset(ibus_controller); 768 ibus_controller_->AddObserver(this); 769} 770 771void InputMethodManagerImpl::SetCandidateWindowControllerForTesting( 772 CandidateWindowController* candidate_window_controller) { 773 candidate_window_controller_.reset(candidate_window_controller); 774 candidate_window_controller_->Init(); 775 candidate_window_controller_->AddObserver(this); 776} 777 778void InputMethodManagerImpl::SetXKeyboardForTesting(XKeyboard* xkeyboard) { 779 xkeyboard_.reset(xkeyboard); 780} 781 782void InputMethodManagerImpl::InitializeComponentExtensionForTesting( 783 scoped_ptr<ComponentExtensionIMEManagerDelegate> delegate) { 784 OnComponentExtensionInitialized(delegate.Pass()); 785} 786 787void InputMethodManagerImpl::PropertyChanged() { 788 FOR_EACH_OBSERVER(InputMethodManager::Observer, 789 observers_, 790 InputMethodPropertyChanged(this)); 791} 792 793void InputMethodManagerImpl::CandidateWindowOpened() { 794 FOR_EACH_OBSERVER(InputMethodManager::CandidateWindowObserver, 795 candidate_window_observers_, 796 CandidateWindowOpened(this)); 797} 798 799void InputMethodManagerImpl::CandidateWindowClosed() { 800 FOR_EACH_OBSERVER(InputMethodManager::CandidateWindowObserver, 801 candidate_window_observers_, 802 CandidateWindowClosed(this)); 803} 804 805void InputMethodManagerImpl::OnScreenLocked() { 806 saved_previous_input_method_ = previous_input_method_; 807 saved_current_input_method_ = current_input_method_; 808 saved_active_input_method_ids_ = active_input_method_ids_; 809 810 const std::string hardware_keyboard_id = util_.GetHardwareInputMethodId(); 811 // We'll add the hardware keyboard if it's not included in 812 // |active_input_method_list| so that the user can always use the hardware 813 // keyboard on the screen locker. 814 bool should_add_hardware_keyboard = true; 815 816 active_input_method_ids_.clear(); 817 for (size_t i = 0; i < saved_active_input_method_ids_.size(); ++i) { 818 const std::string& input_method_id = saved_active_input_method_ids_[i]; 819 // Skip if it's not a keyboard layout. Drop input methods including 820 // extension ones. 821 if (!InputMethodUtil::IsKeyboardLayout(input_method_id)) 822 continue; 823 active_input_method_ids_.push_back(input_method_id); 824 if (input_method_id == hardware_keyboard_id) 825 should_add_hardware_keyboard = false; 826 } 827 if (should_add_hardware_keyboard) 828 active_input_method_ids_.push_back(hardware_keyboard_id); 829 830 ChangeInputMethod(current_input_method_.id()); 831} 832 833void InputMethodManagerImpl::OnScreenUnlocked() { 834 previous_input_method_ = saved_previous_input_method_; 835 current_input_method_ = saved_current_input_method_; 836 active_input_method_ids_ = saved_active_input_method_ids_; 837 838 ChangeInputMethod(current_input_method_.id()); 839} 840 841bool InputMethodManagerImpl::InputMethodIsActivated( 842 const std::string& input_method_id) { 843 return Contains(active_input_method_ids_, input_method_id); 844} 845 846bool InputMethodManagerImpl::ContainOnlyKeyboardLayout( 847 const std::vector<std::string>& value) { 848 for (size_t i = 0; i < value.size(); ++i) { 849 if (!InputMethodUtil::IsKeyboardLayout(value[i])) 850 return false; 851 } 852 return true; 853} 854 855void InputMethodManagerImpl::MaybeInitializeCandidateWindowController() { 856 if (candidate_window_controller_.get()) 857 return; 858 859 candidate_window_controller_.reset( 860 CandidateWindowController::CreateCandidateWindowController()); 861 if (candidate_window_controller_->Init()) 862 candidate_window_controller_->AddObserver(this); 863 else 864 DVLOG(1) << "Failed to initialize the candidate window controller"; 865} 866 867bool InputMethodManagerImpl::IsIBusConnectionAlive() { 868 return DBusThreadManager::Get() && DBusThreadManager::Get()->GetIBusClient(); 869} 870 871} // namespace input_method 872} // namespace chromeos 873