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