input_method_manager_impl.cc revision 5e3f23d412006dc4db4e659864679f29341e113f
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/strings/string_util.h" 14#include "base/strings/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 std::vector<std::string>::iterator it = 259 std::unique(input_method_ids->begin(), input_method_ids->end()); 260 input_method_ids->resize(std::distance(input_method_ids->begin(), it)); 261 return rewritten; 262} 263 264bool InputMethodManagerImpl::SetInputMethodConfig( 265 const std::string& section, 266 const std::string& config_name, 267 const InputMethodConfigValue& value) { 268 DCHECK(section != language_prefs::kGeneralSectionName || 269 config_name != language_prefs::kPreloadEnginesConfigName); 270 271 if (state_ == STATE_TERMINATING) 272 return false; 273 return ibus_controller_->SetInputMethodConfig(section, config_name, value); 274} 275 276void InputMethodManagerImpl::ChangeInputMethod( 277 const std::string& input_method_id) { 278 ChangeInputMethodInternal(input_method_id, false); 279} 280 281bool InputMethodManagerImpl::ChangeInputMethodInternal( 282 const std::string& input_method_id, 283 bool show_message) { 284 if (state_ == STATE_TERMINATING) 285 return false; 286 287 std::string input_method_id_to_switch = input_method_id; 288 289 // Sanity check. 290 if (!InputMethodIsActivated(input_method_id)) { 291 scoped_ptr<InputMethodDescriptors> input_methods(GetActiveInputMethods()); 292 DCHECK(!input_methods->empty()); 293 input_method_id_to_switch = input_methods->at(0).id(); 294 if (!input_method_id.empty()) { 295 DVLOG(1) << "Can't change the current input method to " 296 << input_method_id << " since the engine is not enabled. " 297 << "Switch to " << input_method_id_to_switch << " instead."; 298 } 299 } 300 301 if (!component_extension_ime_manager_->IsInitialized() || 302 (!InputMethodUtil::IsKeyboardLayout(input_method_id_to_switch) && 303 !IsIBusConnectionAlive())) { 304 // We can't change input method before the initialization of component 305 // extension ime manager or before connection to ibus-daemon is not 306 // established. ChangeInputMethod will be called with 307 // |pending_input_method_| when the both initialization is done. 308 pending_input_method_ = input_method_id_to_switch; 309 return false; 310 } 311 312 pending_input_method_.clear(); 313 IBusInputContextClient* input_context = 314 chromeos::DBusThreadManager::Get()->GetIBusInputContextClient(); 315 const std::string current_input_method_id = current_input_method_.id(); 316 IBusClient* client = DBusThreadManager::Get()->GetIBusClient(); 317 if (InputMethodUtil::IsKeyboardLayout(input_method_id_to_switch)) { 318 FOR_EACH_OBSERVER(InputMethodManager::Observer, 319 observers_, 320 InputMethodPropertyChanged(this)); 321 // Hack for fixing http://crosbug.com/p/12798 322 // We should notify IME switching to ibus-daemon, otherwise 323 // IBusPreeditFocusMode does not work. To achieve it, change engine to 324 // itself if the next engine is XKB layout. 325 if (current_input_method_id.empty() || 326 InputMethodUtil::IsKeyboardLayout(current_input_method_id)) { 327 if (input_context) 328 input_context->Reset(); 329 } else { 330 if (client) 331 client->SetGlobalEngine(current_input_method_id, 332 base::Bind(&base::DoNothing)); 333 } 334 if (input_context) 335 input_context->SetIsXKBLayout(true); 336 } else { 337 DCHECK(client); 338 client->SetGlobalEngine(input_method_id_to_switch, 339 base::Bind(&base::DoNothing)); 340 if (input_context) 341 input_context->SetIsXKBLayout(false); 342 } 343 344 if (current_input_method_id != input_method_id_to_switch) { 345 // Clear input method properties unconditionally if 346 // |input_method_id_to_switch| is not equal to |current_input_method_id|. 347 // 348 // When switching to another input method and no text area is focused, 349 // RegisterProperties signal for the new input method will NOT be sent 350 // until a text area is focused. Therefore, we have to clear the old input 351 // method properties here to keep the input method switcher status 352 // consistent. 353 // 354 // When |input_method_id_to_switch| and |current_input_method_id| are the 355 // same, the properties shouldn't be cleared. If we do that, something 356 // wrong happens in step #4 below: 357 // 1. Enable "xkb:us::eng" and "mozc". Switch to "mozc". 358 // 2. Focus Omnibox. IME properties for mozc are sent to Chrome. 359 // 3. Switch to "xkb:us::eng". No function in this file is called. 360 // 4. Switch back to "mozc". ChangeInputMethod("mozc") is called, but it's 361 // basically NOP since ibus-daemon's current IME is already "mozc". 362 // IME properties are not sent to Chrome for the same reason. 363 // TODO(nona): Revisit above comment once ibus-daemon is gone. 364 ibus_controller_->ClearProperties(); 365 366 const InputMethodDescriptor* descriptor = NULL; 367 if (!extension_ime_util::IsExtensionIME(input_method_id_to_switch)) { 368 descriptor = 369 util_.GetInputMethodDescriptorFromId(input_method_id_to_switch); 370 } else { 371 std::map<std::string, InputMethodDescriptor>::const_iterator i = 372 extra_input_methods_.find(input_method_id_to_switch); 373 DCHECK(i != extra_input_methods_.end()); 374 descriptor = &(i->second); 375 } 376 DCHECK(descriptor); 377 378 previous_input_method_ = current_input_method_; 379 current_input_method_ = *descriptor; 380 } 381 382 // Change the keyboard layout to a preferred layout for the input method. 383 if (!xkeyboard_->SetCurrentKeyboardLayoutByName( 384 current_input_method_.GetPreferredKeyboardLayout())) { 385 LOG(ERROR) << "Failed to change keyboard layout to " 386 << current_input_method_.GetPreferredKeyboardLayout(); 387 } 388 389 // Update input method indicators (e.g. "US", "DV") in Chrome windows. 390 FOR_EACH_OBSERVER(InputMethodManager::Observer, 391 observers_, 392 InputMethodChanged(this, show_message)); 393 return true; 394} 395 396void InputMethodManagerImpl::OnComponentExtensionInitialized( 397 scoped_ptr<ComponentExtensionIMEManagerDelegate> delegate) { 398 DCHECK(thread_checker_.CalledOnValidThread()); 399 component_extension_ime_manager_->Initialize(delegate.Pass()); 400 util_.SetComponentExtensions( 401 component_extension_ime_manager_->GetAllIMEAsInputMethodDescriptor()); 402 403 LoadNecessaryComponentExtensions(); 404 405 if (!pending_input_method_.empty()) 406 ChangeInputMethodInternal(pending_input_method_, false); 407 408} 409 410void InputMethodManagerImpl::LoadNecessaryComponentExtensions() { 411 if (!component_extension_ime_manager_->IsInitialized()) 412 return; 413 // Load component extensions but also update |active_input_method_ids_| as 414 // some component extension IMEs may have been removed from the Chrome OS 415 // image. If specified component extension IME no longer exists, falling back 416 // to an existing IME. 417 std::vector<std::string> unfiltered_input_method_ids = 418 active_input_method_ids_; 419 active_input_method_ids_.clear(); 420 for (size_t i = 0; i < unfiltered_input_method_ids.size(); ++i) { 421 if (!component_extension_ime_manager_->IsComponentExtensionIMEId( 422 unfiltered_input_method_ids[i])) { 423 // Legacy IMEs or xkb layouts are alwayes active. 424 active_input_method_ids_.push_back(unfiltered_input_method_ids[i]); 425 } else if (component_extension_ime_manager_->IsWhitelisted( 426 unfiltered_input_method_ids[i])) { 427 component_extension_ime_manager_->LoadComponentExtensionIME( 428 unfiltered_input_method_ids[i]); 429 active_input_method_ids_.push_back(unfiltered_input_method_ids[i]); 430 } 431 } 432} 433 434void InputMethodManagerImpl::ActivateInputMethodProperty( 435 const std::string& key) { 436 DCHECK(!key.empty()); 437 ibus_controller_->ActivateInputMethodProperty(key); 438} 439 440void InputMethodManagerImpl::AddInputMethodExtension( 441 const std::string& id, 442 const std::string& name, 443 const std::vector<std::string>& layouts, 444 const std::string& language, 445 const GURL& options_url, 446 InputMethodEngine* engine) { 447 if (state_ == STATE_TERMINATING) 448 return; 449 450 if (!extension_ime_util::IsExtensionIME(id) && 451 !ComponentExtensionIMEManager::IsComponentExtensionIMEId(id)) { 452 DVLOG(1) << id << " is not a valid extension input method ID."; 453 return; 454 } 455 456 extra_input_methods_[id] = 457 InputMethodDescriptor(id, name, layouts, language, options_url); 458 if (Contains(enabled_extension_imes_, id) && 459 !ComponentExtensionIMEManager::IsComponentExtensionIMEId(id)) { 460 if (!Contains(active_input_method_ids_, id)) { 461 active_input_method_ids_.push_back(id); 462 } else { 463 DVLOG(1) << "AddInputMethodExtension: alread added: " 464 << id << ", " << name; 465 // Call Start() anyway, just in case. 466 } 467 468 // Ensure that the input method daemon is running. 469 MaybeInitializeCandidateWindowController(); 470 IBusDaemonController::GetInstance()->Start(); 471 } 472 473 extra_input_method_instances_[id] = 474 static_cast<InputMethodEngineIBus*>(engine); 475} 476 477void InputMethodManagerImpl::RemoveInputMethodExtension(const std::string& id) { 478 if (!extension_ime_util::IsExtensionIME(id)) 479 DVLOG(1) << id << " is not a valid extension input method ID."; 480 481 std::vector<std::string>::iterator i = std::find( 482 active_input_method_ids_.begin(), active_input_method_ids_.end(), id); 483 if (i != active_input_method_ids_.end()) 484 active_input_method_ids_.erase(i); 485 extra_input_methods_.erase(id); 486 487 if (ContainOnlyKeyboardLayout(active_input_method_ids_)) { 488 // Do NOT call ibus_controller_->Stop(); here to work around a crash issue 489 // at crosbug.com/27051. 490 // TODO(yusukes): We can safely call Stop(); here once crosbug.com/26443 491 // is implemented. 492 } 493 494 // If |current_input_method| is no longer in |active_input_method_ids_|, 495 // switch to the first one in |active_input_method_ids_|. 496 ChangeInputMethod(current_input_method_.id()); 497 498 std::map<std::string, InputMethodEngineIBus*>::iterator ite = 499 extra_input_method_instances_.find(id); 500 if (ite == extra_input_method_instances_.end()) { 501 DVLOG(1) << "The engine instance of " << id << " has already gone."; 502 } else { 503 // Do NOT release the actual instance here. This class does not take an 504 // onwership of engine instance. 505 extra_input_method_instances_.erase(ite); 506 } 507} 508 509void InputMethodManagerImpl::GetInputMethodExtensions( 510 InputMethodDescriptors* result) { 511 // Build the extension input method descriptors from the extra input 512 // methods cache |extra_input_methods_|. 513 std::map<std::string, InputMethodDescriptor>::iterator iter; 514 for (iter = extra_input_methods_.begin(); iter != extra_input_methods_.end(); 515 ++iter) { 516 if (extension_ime_util::IsExtensionIME(iter->first)) 517 result->push_back(iter->second); 518 } 519} 520 521void InputMethodManagerImpl::SetEnabledExtensionImes( 522 std::vector<std::string>* ids) { 523 enabled_extension_imes_.clear(); 524 enabled_extension_imes_.insert(enabled_extension_imes_.end(), 525 ids->begin(), 526 ids->end()); 527 528 bool active_imes_changed = false; 529 530 for (std::map<std::string, InputMethodDescriptor>::iterator extra_iter = 531 extra_input_methods_.begin(); extra_iter != extra_input_methods_.end(); 532 ++extra_iter) { 533 if (ComponentExtensionIMEManager::IsComponentExtensionIMEId( 534 extra_iter->first)) 535 continue; // Do not filter component extension. 536 std::vector<std::string>::iterator active_iter = std::find( 537 active_input_method_ids_.begin(), active_input_method_ids_.end(), 538 extra_iter->first); 539 540 bool active = active_iter != active_input_method_ids_.end(); 541 bool enabled = Contains(enabled_extension_imes_, extra_iter->first); 542 543 if (active && !enabled) 544 active_input_method_ids_.erase(active_iter); 545 546 if (!active && enabled) 547 active_input_method_ids_.push_back(extra_iter->first); 548 549 if (active == !enabled) 550 active_imes_changed = true; 551 } 552 553 if (active_imes_changed) { 554 MaybeInitializeCandidateWindowController(); 555 IBusDaemonController::GetInstance()->Start(); 556 557 // If |current_input_method| is no longer in |active_input_method_ids_|, 558 // switch to the first one in |active_input_method_ids_|. 559 ChangeInputMethod(current_input_method_.id()); 560 } 561} 562 563bool InputMethodManagerImpl::SwitchToNextInputMethod() { 564 // Sanity checks. 565 if (active_input_method_ids_.empty()) { 566 DVLOG(1) << "active input method is empty"; 567 return false; 568 } 569 if (current_input_method_.id().empty()) { 570 DVLOG(1) << "current_input_method_ is unknown"; 571 return false; 572 } 573 574 // Do not consume key event if there is only one input method is enabled. 575 // Ctrl+Space or Alt+Shift may be used by other application. 576 if (active_input_method_ids_.size() == 1) 577 return false; 578 579 // Find the next input method and switch to it. 580 SwitchToNextInputMethodInternal(active_input_method_ids_, 581 current_input_method_.id()); 582 return true; 583} 584 585bool InputMethodManagerImpl::SwitchToPreviousInputMethod() { 586 // Sanity check. 587 if (active_input_method_ids_.empty()) { 588 DVLOG(1) << "active input method is empty"; 589 return false; 590 } 591 592 // Do not consume key event if there is only one input method is enabled. 593 // Ctrl+Space or Alt+Shift may be used by other application. 594 if (active_input_method_ids_.size() == 1) 595 return false; 596 597 if (previous_input_method_.id().empty() || 598 previous_input_method_.id() == current_input_method_.id()) { 599 return SwitchToNextInputMethod(); 600 } 601 602 std::vector<std::string>::const_iterator iter = 603 std::find(active_input_method_ids_.begin(), 604 active_input_method_ids_.end(), 605 previous_input_method_.id()); 606 if (iter == active_input_method_ids_.end()) { 607 // previous_input_method_ is not supported. 608 return SwitchToNextInputMethod(); 609 } 610 ChangeInputMethodInternal(*iter, true); 611 return true; 612} 613 614bool InputMethodManagerImpl::SwitchInputMethod( 615 const ui::Accelerator& accelerator) { 616 // Sanity check. 617 if (active_input_method_ids_.empty()) { 618 DVLOG(1) << "active input method is empty"; 619 return false; 620 } 621 622 // Get the list of input method ids for the |accelerator|. For example, get 623 // { "mozc-hangul", "xkb:kr:kr104:kor" } for ui::VKEY_DBE_SBCSCHAR. 624 std::vector<std::string> input_method_ids_to_switch; 625 switch (accelerator.key_code()) { 626 case ui::VKEY_CONVERT: // Henkan key on JP106 keyboard 627 input_method_ids_to_switch.push_back(nacl_mozc_jp_id); 628 break; 629 case ui::VKEY_NONCONVERT: // Muhenkan key on JP106 keyboard 630 input_method_ids_to_switch.push_back("xkb:jp::jpn"); 631 break; 632 case ui::VKEY_DBE_SBCSCHAR: // ZenkakuHankaku key on JP106 keyboard 633 case ui::VKEY_DBE_DBCSCHAR: 634 input_method_ids_to_switch.push_back(nacl_mozc_jp_id); 635 input_method_ids_to_switch.push_back("xkb:jp::jpn"); 636 break; 637 case ui::VKEY_HANGUL: // Hangul (or right Alt) key on Korean keyboard 638 input_method_ids_to_switch.push_back("mozc-hangul"); 639 input_method_ids_to_switch.push_back("xkb:kr:kr104:kor"); 640 break; 641 default: 642 NOTREACHED(); 643 break; 644 } 645 if (input_method_ids_to_switch.empty()) { 646 DVLOG(1) << "Unexpected VKEY: " << accelerator.key_code(); 647 return false; 648 } 649 650 // Obtain the intersection of input_method_ids_to_switch and 651 // active_input_method_ids_. The order of IDs in active_input_method_ids_ is 652 // preserved. 653 std::vector<std::string> ids; 654 for (size_t i = 0; i < input_method_ids_to_switch.size(); ++i) { 655 const std::string& id = input_method_ids_to_switch[i]; 656 if (Contains(active_input_method_ids_, id)) 657 ids.push_back(id); 658 } 659 if (ids.empty()) { 660 // No input method for the accelerator is active. For example, we should 661 // just ignore VKEY_HANGUL when mozc-hangul is not active. 662 return false; 663 } 664 665 SwitchToNextInputMethodInternal(ids, current_input_method_.id()); 666 return true; // consume the accelerator. 667} 668 669void InputMethodManagerImpl::SwitchToNextInputMethodInternal( 670 const std::vector<std::string>& input_method_ids, 671 const std::string& current_input_method_id) { 672 std::vector<std::string>::const_iterator iter = 673 std::find(input_method_ids.begin(), 674 input_method_ids.end(), 675 current_input_method_id); 676 if (iter != input_method_ids.end()) 677 ++iter; 678 if (iter == input_method_ids.end()) 679 iter = input_method_ids.begin(); 680 ChangeInputMethodInternal(*iter, true); 681} 682 683InputMethodDescriptor InputMethodManagerImpl::GetCurrentInputMethod() const { 684 if (current_input_method_.id().empty()) 685 return InputMethodUtil::GetFallbackInputMethodDescriptor(); 686 return current_input_method_; 687} 688 689InputMethodPropertyList 690InputMethodManagerImpl::GetCurrentInputMethodProperties() const { 691 // This check is necessary since an IME property (e.g. for Pinyin) might be 692 // sent from ibus-daemon AFTER the current input method is switched to XKB. 693 if (InputMethodUtil::IsKeyboardLayout(GetCurrentInputMethod().id())) 694 return InputMethodPropertyList(); 695 return ibus_controller_->GetCurrentProperties(); 696} 697 698XKeyboard* InputMethodManagerImpl::GetXKeyboard() { 699 return xkeyboard_.get(); 700} 701 702InputMethodUtil* InputMethodManagerImpl::GetInputMethodUtil() { 703 return &util_; 704} 705 706ComponentExtensionIMEManager* 707 InputMethodManagerImpl::GetComponentExtensionIMEManager() { 708 DCHECK(thread_checker_.CalledOnValidThread()); 709 return component_extension_ime_manager_.get(); 710} 711 712void InputMethodManagerImpl::OnConnected() { 713 for (std::map<std::string, InputMethodEngineIBus*>::iterator ite = 714 extra_input_method_instances_.begin(); 715 ite != extra_input_method_instances_.end(); 716 ite++) { 717 if (Contains(enabled_extension_imes_, ite->first) || 718 (component_extension_ime_manager_->IsInitialized() && 719 component_extension_ime_manager_->IsWhitelisted(ite->first))) { 720 ite->second->OnConnected(); 721 } 722 } 723 724 if (!pending_input_method_.empty()) 725 ChangeInputMethodInternal(pending_input_method_, false); 726} 727 728void InputMethodManagerImpl::OnDisconnected() { 729 for (std::map<std::string, InputMethodEngineIBus*>::iterator ite = 730 extra_input_method_instances_.begin(); 731 ite != extra_input_method_instances_.end(); 732 ite++) { 733 if (Contains(enabled_extension_imes_, ite->first)) 734 ite->second->OnDisconnected(); 735 } 736} 737 738void InputMethodManagerImpl::InitializeComponentExtension() { 739 ComponentExtensionIMEManagerImpl* impl = 740 new ComponentExtensionIMEManagerImpl(); 741 scoped_ptr<ComponentExtensionIMEManagerDelegate> delegate(impl); 742 impl->InitializeAsync(base::Bind( 743 &InputMethodManagerImpl::OnComponentExtensionInitialized, 744 weak_ptr_factory_.GetWeakPtr(), 745 base::Passed(&delegate))); 746} 747 748void InputMethodManagerImpl::Init(base::SequencedTaskRunner* ui_task_runner) { 749 DCHECK(!ibus_controller_.get()); 750 DCHECK(thread_checker_.CalledOnValidThread()); 751 752 ibus_controller_.reset(IBusController::Create()); 753 xkeyboard_.reset(XKeyboard::Create()); 754 ibus_controller_->AddObserver(this); 755 756 // We can't call impl->Initialize here, because file thread is not available 757 // at this moment. 758 ui_task_runner->PostTask( 759 FROM_HERE, 760 base::Bind(&InputMethodManagerImpl::InitializeComponentExtension, 761 weak_ptr_factory_.GetWeakPtr())); 762} 763 764void InputMethodManagerImpl::SetIBusControllerForTesting( 765 IBusController* ibus_controller) { 766 ibus_controller_.reset(ibus_controller); 767 ibus_controller_->AddObserver(this); 768} 769 770void InputMethodManagerImpl::SetCandidateWindowControllerForTesting( 771 CandidateWindowController* candidate_window_controller) { 772 candidate_window_controller_.reset(candidate_window_controller); 773 candidate_window_controller_->Init(); 774 candidate_window_controller_->AddObserver(this); 775} 776 777void InputMethodManagerImpl::SetXKeyboardForTesting(XKeyboard* xkeyboard) { 778 xkeyboard_.reset(xkeyboard); 779} 780 781void InputMethodManagerImpl::InitializeComponentExtensionForTesting( 782 scoped_ptr<ComponentExtensionIMEManagerDelegate> delegate) { 783 OnComponentExtensionInitialized(delegate.Pass()); 784} 785 786void InputMethodManagerImpl::PropertyChanged() { 787 FOR_EACH_OBSERVER(InputMethodManager::Observer, 788 observers_, 789 InputMethodPropertyChanged(this)); 790} 791 792void InputMethodManagerImpl::CandidateWindowOpened() { 793 FOR_EACH_OBSERVER(InputMethodManager::CandidateWindowObserver, 794 candidate_window_observers_, 795 CandidateWindowOpened(this)); 796} 797 798void InputMethodManagerImpl::CandidateWindowClosed() { 799 FOR_EACH_OBSERVER(InputMethodManager::CandidateWindowObserver, 800 candidate_window_observers_, 801 CandidateWindowClosed(this)); 802} 803 804void InputMethodManagerImpl::OnScreenLocked() { 805 saved_previous_input_method_ = previous_input_method_; 806 saved_current_input_method_ = current_input_method_; 807 saved_active_input_method_ids_ = active_input_method_ids_; 808 809 const std::string hardware_keyboard_id = util_.GetHardwareInputMethodId(); 810 // We'll add the hardware keyboard if it's not included in 811 // |active_input_method_list| so that the user can always use the hardware 812 // keyboard on the screen locker. 813 bool should_add_hardware_keyboard = true; 814 815 active_input_method_ids_.clear(); 816 for (size_t i = 0; i < saved_active_input_method_ids_.size(); ++i) { 817 const std::string& input_method_id = saved_active_input_method_ids_[i]; 818 // Skip if it's not a keyboard layout. Drop input methods including 819 // extension ones. 820 if (!InputMethodUtil::IsKeyboardLayout(input_method_id)) 821 continue; 822 active_input_method_ids_.push_back(input_method_id); 823 if (input_method_id == hardware_keyboard_id) 824 should_add_hardware_keyboard = false; 825 } 826 if (should_add_hardware_keyboard) 827 active_input_method_ids_.push_back(hardware_keyboard_id); 828 829 ChangeInputMethod(current_input_method_.id()); 830} 831 832void InputMethodManagerImpl::OnScreenUnlocked() { 833 previous_input_method_ = saved_previous_input_method_; 834 current_input_method_ = saved_current_input_method_; 835 active_input_method_ids_ = saved_active_input_method_ids_; 836 837 ChangeInputMethod(current_input_method_.id()); 838} 839 840bool InputMethodManagerImpl::InputMethodIsActivated( 841 const std::string& input_method_id) { 842 return Contains(active_input_method_ids_, input_method_id); 843} 844 845bool InputMethodManagerImpl::ContainOnlyKeyboardLayout( 846 const std::vector<std::string>& value) { 847 for (size_t i = 0; i < value.size(); ++i) { 848 if (!InputMethodUtil::IsKeyboardLayout(value[i])) 849 return false; 850 } 851 return true; 852} 853 854void InputMethodManagerImpl::MaybeInitializeCandidateWindowController() { 855 if (candidate_window_controller_.get()) 856 return; 857 858 candidate_window_controller_.reset( 859 CandidateWindowController::CreateCandidateWindowController()); 860 if (candidate_window_controller_->Init()) 861 candidate_window_controller_->AddObserver(this); 862 else 863 DVLOG(1) << "Failed to initialize the candidate window controller"; 864} 865 866bool InputMethodManagerImpl::IsIBusConnectionAlive() { 867 return DBusThreadManager::Get() && DBusThreadManager::Get()->GetIBusClient(); 868} 869 870} // namespace input_method 871} // namespace chromeos 872