| // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/chromeos/input_method/input_method_manager_impl.h" |
| |
| #include <algorithm> // std::find |
| |
| #include "base/basictypes.h" |
| #include "base/bind.h" |
| #include "base/location.h" |
| #include "base/memory/scoped_ptr.h" |
| #include "base/strings/string_util.h" |
| #include "base/strings/stringprintf.h" |
| #include "chrome/browser/chromeos/input_method/candidate_window_controller.h" |
| #include "chrome/browser/chromeos/input_method/component_extension_ime_manager_impl.h" |
| #include "chrome/browser/chromeos/input_method/input_method_engine_ibus.h" |
| #include "chrome/browser/chromeos/language_preferences.h" |
| #include "chromeos/dbus/dbus_thread_manager.h" |
| #include "chromeos/dbus/ibus/ibus_client.h" |
| #include "chromeos/dbus/ibus/ibus_input_context_client.h" |
| #include "chromeos/ime/component_extension_ime_manager.h" |
| #include "chromeos/ime/extension_ime_util.h" |
| #include "chromeos/ime/input_method_delegate.h" |
| #include "chromeos/ime/xkeyboard.h" |
| #include "third_party/icu/source/common/unicode/uloc.h" |
| #include "ui/base/accelerators/accelerator.h" |
| |
| namespace chromeos { |
| namespace input_method { |
| |
| namespace { |
| |
| const char nacl_mozc_us_id[] = |
| "_comp_ime_fpfbhcjppmaeaijcidgiibchfbnhbeljnacl_mozc_us"; |
| const char nacl_mozc_jp_id[] = |
| "_comp_ime_fpfbhcjppmaeaijcidgiibchfbnhbeljnacl_mozc_jp"; |
| |
| bool Contains(const std::vector<std::string>& container, |
| const std::string& value) { |
| return std::find(container.begin(), container.end(), value) != |
| container.end(); |
| } |
| |
| const struct MigrationInputMethodList { |
| const char* old_input_method; |
| const char* new_input_method; |
| } kMigrationInputMethodList[] = { |
| { "mozc", "_comp_ime_fpfbhcjppmaeaijcidgiibchfbnhbeljnacl_mozc_us" }, |
| { "mozc-jp", "_comp_ime_fpfbhcjppmaeaijcidgiibchfbnhbeljnacl_mozc_jp" }, |
| { "mozc-dv", "_comp_ime_fpfbhcjppmaeaijcidgiibchfbnhbeljnacl_mozc_us" }, |
| { "pinyin", "_comp_ime_nmblnjkfdkabgdofidlkienfnnbjhnabzh-t-i0-pinyin" }, |
| { "pinyin-dv", "_comp_ime_nmblnjkfdkabgdofidlkienfnnbjhnabzh-t-i0-pinyin" }, |
| { "mozc-chewing", |
| "_comp_ime_ekbifjdfhkmdeeajnolmgdlmkllopefizh-hant-t-i0-und "}, |
| { "m17n:zh:cangjie", |
| "_comp_ime_gjhclobljhjhgoebiipblnmdodbmpdgdzh-hant-t-i0-cangjie-1987" }, |
| { "_comp_ime_jcffnbbngddhenhcnebafkbdomehdhpdzh-t-i0-wubi-1986", |
| "_comp_ime_gjhclobljhjhgoebiipblnmdodbmpdgdzh-t-i0-wubi-1986" }, |
| // TODO(nona): Remove following migration map in M31. |
| { "m17n:ta:itrans", |
| "_comp_ime_jhffeifommiaekmbkkjlpmilogcfdohpvkd_ta_itrans" }, |
| { "m17n:ta:tamil99", |
| "_comp_ime_jhffeifommiaekmbkkjlpmilogcfdohpvkd_ta_tamil99" }, |
| { "m17n:ta:typewriter", |
| "_comp_ime_jhffeifommiaekmbkkjlpmilogcfdohpvkd_ta_typewriter" }, |
| { "m17n:ta:inscript", |
| "_comp_ime_jhffeifommiaekmbkkjlpmilogcfdohpvkd_ta_phone" }, |
| { "m17n:ta:phonetic", |
| "_comp_ime_jhffeifommiaekmbkkjlpmilogcfdohpvkd_ta_inscript" }, |
| { "m17n:th:pattachote", |
| "_comp_ime_jhffeifommiaekmbkkjlpmilogcfdohpvkd_th_pattajoti" }, |
| { "m17n:th:tis820", "_comp_ime_jhffeifommiaekmbkkjlpmilogcfdohpvkd_th_tis" }, |
| { "m17n:th:kesmanee", |
| "_comp_ime_jhffeifommiaekmbkkjlpmilogcfdohpvkd_th" }, |
| { "m17n:vi:tcvn", "_comp_ime_jhffeifommiaekmbkkjlpmilogcfdohpvkd_vi_tcvn" }, |
| { "m17n:vi:viqr", "_comp_ime_jhffeifommiaekmbkkjlpmilogcfdohpvkd_vi_viqr" }, |
| { "m17n:vi:telex", |
| "_comp_ime_jhffeifommiaekmbkkjlpmilogcfdohpvkd_vi_telex" }, |
| { "m17n:vi:vni", |
| "_comp_ime_jhffeifommiaekmbkkjlpmilogcfdohpvkd_vi_vni" }, |
| { "m17n:am:sera", |
| "_comp_ime_jhffeifommiaekmbkkjlpmilogcfdohpvkd_ethi" }, |
| { "m17n:bn:itrans", |
| "_comp_ime_jhffeifommiaekmbkkjlpmilogcfdohpvkd_bn_phone" }, |
| { "m17n:gu:itrans", |
| "_comp_ime_jhffeifommiaekmbkkjlpmilogcfdohpvkd_gu_phone" }, |
| { "m17n:hi:itrans", |
| "_comp_ime_jhffeifommiaekmbkkjlpmilogcfdohpvkd_deva_phone" }, |
| { "m17n:kn:itrans", |
| "_comp_ime_jhffeifommiaekmbkkjlpmilogcfdohpvkd_kn_phone" }, |
| { "m17n:ml:itrans", |
| "_comp_ime_jhffeifommiaekmbkkjlpmilogcfdohpvkd_ml_phone" }, |
| { "m17n:mr:itrans", |
| "_comp_ime_jhffeifommiaekmbkkjlpmilogcfdohpvkd_deva_phone" }, |
| { "m17n:te:itrans", |
| "_comp_ime_jhffeifommiaekmbkkjlpmilogcfdohpvkd_te_phone" }, |
| { "m17n:fa:isiri", "_comp_ime_jhffeifommiaekmbkkjlpmilogcfdohpvkd_fa" }, |
| { "m17n:ar:kbd", "_comp_ime_jhffeifommiaekmbkkjlpmilogcfdohpvkd_ar" }, |
| // TODO(nona): Remove following migration map in M32 |
| { "m17n:zh:quick", |
| "_comp_ime_ekbifjdfhkmdeeajnolmgdlmkllopefizh-hant-t-i0-und" }, |
| }; |
| |
| const struct MigrationHangulKeyboardToInputMethodID { |
| const char* keyboard_id; |
| const char* ime_id; |
| } kMigrationHangulKeyboardToInputMethodID[] = { |
| { "2", "_comp_ime_bdgdidmhaijohebebipajioienkglgfohangul_2set" }, |
| { "3f", "_comp_ime_bdgdidmhaijohebebipajioienkglgfohangul_3setfinal" }, |
| { "39", "_comp_ime_bdgdidmhaijohebebipajioienkglgfohangul_3set390" }, |
| { "3s", "_comp_ime_bdgdidmhaijohebebipajioienkglgfohangul_3setnoshift" }, |
| { "ro", "_comp_ime_bdgdidmhaijohebebipajioienkglgfohangul_romaja" }, |
| }; |
| |
| } // namespace |
| |
| bool InputMethodManagerImpl::IsFullLatinKeyboard( |
| const std::string& layout) const { |
| const std::string& lang = util_.GetLanguageCodeFromInputMethodId(layout); |
| return full_latin_keyboard_checker.IsFullLatinKeyboard(layout, lang); |
| } |
| |
| InputMethodManagerImpl::InputMethodManagerImpl( |
| scoped_ptr<InputMethodDelegate> delegate) |
| : delegate_(delegate.Pass()), |
| state_(STATE_LOGIN_SCREEN), |
| util_(delegate_.get(), GetSupportedInputMethods()), |
| component_extension_ime_manager_(new ComponentExtensionIMEManager()), |
| weak_ptr_factory_(this) { |
| IBusDaemonController::GetInstance()->AddObserver(this); |
| } |
| |
| InputMethodManagerImpl::~InputMethodManagerImpl() { |
| if (ibus_controller_.get()) |
| ibus_controller_->RemoveObserver(this); |
| IBusDaemonController::GetInstance()->RemoveObserver(this); |
| if (candidate_window_controller_.get()) { |
| candidate_window_controller_->RemoveObserver(this); |
| candidate_window_controller_->Shutdown(); |
| } |
| } |
| |
| void InputMethodManagerImpl::AddObserver( |
| InputMethodManager::Observer* observer) { |
| observers_.AddObserver(observer); |
| } |
| |
| void InputMethodManagerImpl::AddCandidateWindowObserver( |
| InputMethodManager::CandidateWindowObserver* observer) { |
| candidate_window_observers_.AddObserver(observer); |
| } |
| |
| void InputMethodManagerImpl::RemoveObserver( |
| InputMethodManager::Observer* observer) { |
| observers_.RemoveObserver(observer); |
| } |
| |
| void InputMethodManagerImpl::RemoveCandidateWindowObserver( |
| InputMethodManager::CandidateWindowObserver* observer) { |
| candidate_window_observers_.RemoveObserver(observer); |
| } |
| |
| void InputMethodManagerImpl::SetState(State new_state) { |
| const State old_state = state_; |
| state_ = new_state; |
| switch (state_) { |
| case STATE_LOGIN_SCREEN: |
| break; |
| case STATE_BROWSER_SCREEN: |
| if (old_state == STATE_LOCK_SCREEN) |
| OnScreenUnlocked(); |
| break; |
| case STATE_LOCK_SCREEN: |
| OnScreenLocked(); |
| break; |
| case STATE_TERMINATING: { |
| if (candidate_window_controller_.get()) { |
| candidate_window_controller_->Shutdown(); |
| candidate_window_controller_.reset(); |
| } |
| break; |
| } |
| } |
| } |
| |
| scoped_ptr<InputMethodDescriptors> |
| InputMethodManagerImpl::GetSupportedInputMethods() const { |
| return whitelist_.GetSupportedInputMethods(); |
| } |
| |
| scoped_ptr<InputMethodDescriptors> |
| InputMethodManagerImpl::GetActiveInputMethods() const { |
| scoped_ptr<InputMethodDescriptors> result(new InputMethodDescriptors); |
| // Build the active input method descriptors from the active input |
| // methods cache |active_input_method_ids_|. |
| for (size_t i = 0; i < active_input_method_ids_.size(); ++i) { |
| const std::string& input_method_id = active_input_method_ids_[i]; |
| const InputMethodDescriptor* descriptor = |
| util_.GetInputMethodDescriptorFromId(input_method_id); |
| if (descriptor) { |
| result->push_back(*descriptor); |
| } else { |
| std::map<std::string, InputMethodDescriptor>::const_iterator ix = |
| extra_input_methods_.find(input_method_id); |
| if (ix != extra_input_methods_.end()) |
| result->push_back(ix->second); |
| else |
| DVLOG(1) << "Descriptor is not found for: " << input_method_id; |
| } |
| } |
| if (result->empty()) { |
| // Initially |active_input_method_ids_| is empty. browser_tests might take |
| // this path. |
| result->push_back( |
| InputMethodUtil::GetFallbackInputMethodDescriptor()); |
| } |
| return result.Pass(); |
| } |
| |
| const std::vector<std::string>& |
| InputMethodManagerImpl::GetActiveInputMethodIds() const { |
| return active_input_method_ids_; |
| } |
| |
| size_t InputMethodManagerImpl::GetNumActiveInputMethods() const { |
| return active_input_method_ids_.size(); |
| } |
| |
| void InputMethodManagerImpl::EnableLayouts(const std::string& language_code, |
| const std::string& initial_layout) { |
| if (state_ == STATE_TERMINATING) |
| return; |
| |
| std::vector<std::string> candidates; |
| // Add input methods associated with the language. |
| util_.GetInputMethodIdsFromLanguageCode(language_code, |
| kKeyboardLayoutsOnly, |
| &candidates); |
| // Add the hardware keyboard as well. We should always add this so users |
| // can use the hardware keyboard on the login screen and the screen locker. |
| candidates.push_back(util_.GetHardwareInputMethodId()); |
| |
| std::vector<std::string> layouts; |
| // First, add the initial input method ID, if it's requested, to |
| // layouts, so it appears first on the list of active input |
| // methods at the input language status menu. |
| if (util_.IsValidInputMethodId(initial_layout) && |
| InputMethodUtil::IsKeyboardLayout(initial_layout)) { |
| layouts.push_back(initial_layout); |
| } else if (!initial_layout.empty()) { |
| DVLOG(1) << "EnableLayouts: ignoring non-keyboard or invalid ID: " |
| << initial_layout; |
| } |
| |
| // Add candidates to layouts, while skipping duplicates. |
| for (size_t i = 0; i < candidates.size(); ++i) { |
| const std::string& candidate = candidates[i]; |
| // Not efficient, but should be fine, as the two vectors are very |
| // short (2-5 items). |
| if (!Contains(layouts, candidate)) |
| layouts.push_back(candidate); |
| } |
| |
| active_input_method_ids_.swap(layouts); |
| ChangeInputMethod(initial_layout); // you can pass empty |initial_layout|. |
| } |
| |
| // Adds new input method to given list. |
| bool InputMethodManagerImpl::EnableInputMethodImpl( |
| const std::string& input_method_id, |
| std::vector<std::string>& new_active_input_method_ids) const { |
| if (!util_.IsValidInputMethodId(input_method_id)) { |
| DVLOG(1) << "EnableInputMethod: Invalid ID: " << input_method_id; |
| return false; |
| } |
| |
| if (!Contains(new_active_input_method_ids, input_method_id)) |
| new_active_input_method_ids.push_back(input_method_id); |
| |
| return true; |
| } |
| |
| // Starts or stops the system input method framework as needed. |
| void InputMethodManagerImpl::ReconfigureIMFramework() { |
| if (component_extension_ime_manager_->IsInitialized()) |
| LoadNecessaryComponentExtensions(); |
| |
| if (ContainsOnlyKeyboardLayout(active_input_method_ids_)) { |
| // Do NOT call ibus_controller_->Stop(); here to work around a crash issue |
| // at crbug.com/27051. |
| // TODO(yusukes): We can safely call Stop(); here once crbug.com/26443 |
| // is implemented. |
| } else { |
| MaybeInitializeCandidateWindowController(); |
| IBusDaemonController::GetInstance()->Start(); |
| } |
| } |
| |
| bool InputMethodManagerImpl::EnableInputMethod( |
| const std::string& input_method_id) { |
| if (!EnableInputMethodImpl(input_method_id, active_input_method_ids_)) |
| return false; |
| |
| ReconfigureIMFramework(); |
| return true; |
| } |
| |
| bool InputMethodManagerImpl::EnableInputMethods( |
| const std::vector<std::string>& new_active_input_method_ids) { |
| if (state_ == STATE_TERMINATING) |
| return false; |
| |
| // Filter unknown or obsolete IDs. |
| std::vector<std::string> new_active_input_method_ids_filtered; |
| |
| for (size_t i = 0; i < new_active_input_method_ids.size(); ++i) |
| EnableInputMethodImpl(new_active_input_method_ids[i], |
| new_active_input_method_ids_filtered); |
| |
| if (new_active_input_method_ids_filtered.empty()) { |
| DVLOG(1) << "EnableInputMethods: No valid input method ID"; |
| return false; |
| } |
| |
| // Copy extension IDs to |new_active_input_method_ids_filtered|. We have to |
| // keep relative order of the extension input method IDs. |
| for (size_t i = 0; i < active_input_method_ids_.size(); ++i) { |
| const std::string& input_method_id = active_input_method_ids_[i]; |
| if (extension_ime_util::IsExtensionIME(input_method_id)) |
| new_active_input_method_ids_filtered.push_back(input_method_id); |
| } |
| active_input_method_ids_.swap(new_active_input_method_ids_filtered); |
| |
| ReconfigureIMFramework(); |
| |
| // If |current_input_method| is no longer in |active_input_method_ids_|, |
| // ChangeInputMethod() picks the first one in |active_input_method_ids_|. |
| ChangeInputMethod(current_input_method_.id()); |
| return true; |
| } |
| |
| bool InputMethodManagerImpl::MigrateOldInputMethods( |
| std::vector<std::string>* input_method_ids) { |
| bool rewritten = false; |
| for (size_t i = 0; i < input_method_ids->size(); ++i) { |
| for (size_t j = 0; j < ARRAYSIZE_UNSAFE(kMigrationInputMethodList); ++j) { |
| if (input_method_ids->at(i) == |
| kMigrationInputMethodList[j].old_input_method) { |
| input_method_ids->at(i).assign( |
| kMigrationInputMethodList[j].new_input_method); |
| rewritten = true; |
| } |
| } |
| } |
| std::vector<std::string>::iterator it = |
| std::unique(input_method_ids->begin(), input_method_ids->end()); |
| input_method_ids->resize(std::distance(input_method_ids->begin(), it)); |
| return rewritten; |
| } |
| |
| bool InputMethodManagerImpl::MigrateKoreanKeyboard( |
| const std::string& keyboard_id, |
| std::vector<std::string>* input_method_ids) { |
| std::vector<std::string>::iterator it = |
| std::find(active_input_method_ids_.begin(), |
| active_input_method_ids_.end(), |
| "mozc-hangul"); |
| if (it == active_input_method_ids_.end()) |
| return false; |
| |
| for (size_t i = 0; |
| i < ARRAYSIZE_UNSAFE(kMigrationHangulKeyboardToInputMethodID); ++i) { |
| if (kMigrationHangulKeyboardToInputMethodID[i].keyboard_id == keyboard_id) { |
| *it = kMigrationHangulKeyboardToInputMethodID[i].ime_id; |
| input_method_ids->assign(active_input_method_ids_.begin(), |
| active_input_method_ids_.end()); |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| bool InputMethodManagerImpl::SetInputMethodConfig( |
| const std::string& section, |
| const std::string& config_name, |
| const InputMethodConfigValue& value) { |
| DCHECK(section != language_prefs::kGeneralSectionName || |
| config_name != language_prefs::kPreloadEnginesConfigName); |
| |
| if (state_ == STATE_TERMINATING) |
| return false; |
| |
| return ibus_controller_->SetInputMethodConfig(section, config_name, value); |
| } |
| |
| void InputMethodManagerImpl::ChangeInputMethod( |
| const std::string& input_method_id) { |
| ChangeInputMethodInternal(input_method_id, false); |
| } |
| |
| bool InputMethodManagerImpl::ChangeInputMethodInternal( |
| const std::string& input_method_id, |
| bool show_message) { |
| if (state_ == STATE_TERMINATING) |
| return false; |
| |
| std::string input_method_id_to_switch = input_method_id; |
| |
| // Sanity check. |
| if (!InputMethodIsActivated(input_method_id)) { |
| scoped_ptr<InputMethodDescriptors> input_methods(GetActiveInputMethods()); |
| DCHECK(!input_methods->empty()); |
| input_method_id_to_switch = input_methods->at(0).id(); |
| if (!input_method_id.empty()) { |
| DVLOG(1) << "Can't change the current input method to " |
| << input_method_id << " since the engine is not enabled. " |
| << "Switch to " << input_method_id_to_switch << " instead."; |
| } |
| } |
| |
| if (!component_extension_ime_manager_->IsInitialized() || |
| (!InputMethodUtil::IsKeyboardLayout(input_method_id_to_switch) && |
| !IsIBusConnectionAlive())) { |
| // We can't change input method before the initialization of component |
| // extension ime manager or before connection to ibus-daemon is not |
| // established. ChangeInputMethod will be called with |
| // |pending_input_method_| when the both initialization is done. |
| pending_input_method_ = input_method_id_to_switch; |
| return false; |
| } |
| |
| pending_input_method_.clear(); |
| IBusInputContextClient* input_context = |
| chromeos::DBusThreadManager::Get()->GetIBusInputContextClient(); |
| const std::string current_input_method_id = current_input_method_.id(); |
| IBusClient* client = DBusThreadManager::Get()->GetIBusClient(); |
| if (InputMethodUtil::IsKeyboardLayout(input_method_id_to_switch)) { |
| FOR_EACH_OBSERVER(InputMethodManager::Observer, |
| observers_, |
| InputMethodPropertyChanged(this)); |
| // Hack for fixing http://crosbug.com/p/12798 |
| // We should notify IME switching to ibus-daemon, otherwise |
| // IBusPreeditFocusMode does not work. To achieve it, change engine to |
| // itself if the next engine is XKB layout. |
| if (current_input_method_id.empty() || |
| InputMethodUtil::IsKeyboardLayout(current_input_method_id)) { |
| if (input_context) |
| input_context->Reset(); |
| } else { |
| if (client) |
| client->SetGlobalEngine(current_input_method_id, |
| base::Bind(&base::DoNothing)); |
| } |
| if (input_context) |
| input_context->SetIsXKBLayout(true); |
| } else { |
| DCHECK(client); |
| client->SetGlobalEngine(input_method_id_to_switch, |
| base::Bind(&base::DoNothing)); |
| if (input_context) |
| input_context->SetIsXKBLayout(false); |
| } |
| |
| if (current_input_method_id != input_method_id_to_switch) { |
| // Clear input method properties unconditionally if |
| // |input_method_id_to_switch| is not equal to |current_input_method_id|. |
| // |
| // When switching to another input method and no text area is focused, |
| // RegisterProperties signal for the new input method will NOT be sent |
| // until a text area is focused. Therefore, we have to clear the old input |
| // method properties here to keep the input method switcher status |
| // consistent. |
| // |
| // When |input_method_id_to_switch| and |current_input_method_id| are the |
| // same, the properties shouldn't be cleared. If we do that, something |
| // wrong happens in step #4 below: |
| // 1. Enable "xkb:us::eng" and "mozc". Switch to "mozc". |
| // 2. Focus Omnibox. IME properties for mozc are sent to Chrome. |
| // 3. Switch to "xkb:us::eng". No function in this file is called. |
| // 4. Switch back to "mozc". ChangeInputMethod("mozc") is called, but it's |
| // basically NOP since ibus-daemon's current IME is already "mozc". |
| // IME properties are not sent to Chrome for the same reason. |
| // TODO(nona): Revisit above comment once ibus-daemon is gone. |
| ibus_controller_->ClearProperties(); |
| |
| const InputMethodDescriptor* descriptor = NULL; |
| if (!extension_ime_util::IsExtensionIME(input_method_id_to_switch)) { |
| descriptor = |
| util_.GetInputMethodDescriptorFromId(input_method_id_to_switch); |
| } else { |
| std::map<std::string, InputMethodDescriptor>::const_iterator i = |
| extra_input_methods_.find(input_method_id_to_switch); |
| DCHECK(i != extra_input_methods_.end()); |
| descriptor = &(i->second); |
| } |
| DCHECK(descriptor); |
| |
| previous_input_method_ = current_input_method_; |
| current_input_method_ = *descriptor; |
| } |
| |
| // Change the keyboard layout to a preferred layout for the input method. |
| if (!xkeyboard_->SetCurrentKeyboardLayoutByName( |
| current_input_method_.GetPreferredKeyboardLayout())) { |
| LOG(ERROR) << "Failed to change keyboard layout to " |
| << current_input_method_.GetPreferredKeyboardLayout(); |
| } |
| |
| // Update input method indicators (e.g. "US", "DV") in Chrome windows. |
| FOR_EACH_OBSERVER(InputMethodManager::Observer, |
| observers_, |
| InputMethodChanged(this, show_message)); |
| return true; |
| } |
| |
| void InputMethodManagerImpl::OnComponentExtensionInitialized( |
| scoped_ptr<ComponentExtensionIMEManagerDelegate> delegate) { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| component_extension_ime_manager_->Initialize(delegate.Pass()); |
| util_.SetComponentExtensions( |
| component_extension_ime_manager_->GetAllIMEAsInputMethodDescriptor()); |
| |
| LoadNecessaryComponentExtensions(); |
| |
| if (!pending_input_method_.empty()) |
| ChangeInputMethodInternal(pending_input_method_, false); |
| |
| } |
| |
| void InputMethodManagerImpl::LoadNecessaryComponentExtensions() { |
| if (!component_extension_ime_manager_->IsInitialized()) |
| return; |
| // Load component extensions but also update |active_input_method_ids_| as |
| // some component extension IMEs may have been removed from the Chrome OS |
| // image. If specified component extension IME no longer exists, falling back |
| // to an existing IME. |
| std::vector<std::string> unfiltered_input_method_ids = |
| active_input_method_ids_; |
| active_input_method_ids_.clear(); |
| for (size_t i = 0; i < unfiltered_input_method_ids.size(); ++i) { |
| if (!component_extension_ime_manager_->IsComponentExtensionIMEId( |
| unfiltered_input_method_ids[i])) { |
| // Legacy IMEs or xkb layouts are alwayes active. |
| active_input_method_ids_.push_back(unfiltered_input_method_ids[i]); |
| } else if (component_extension_ime_manager_->IsWhitelisted( |
| unfiltered_input_method_ids[i])) { |
| component_extension_ime_manager_->LoadComponentExtensionIME( |
| unfiltered_input_method_ids[i]); |
| active_input_method_ids_.push_back(unfiltered_input_method_ids[i]); |
| } |
| } |
| } |
| |
| void InputMethodManagerImpl::ActivateInputMethodProperty( |
| const std::string& key) { |
| DCHECK(!key.empty()); |
| ibus_controller_->ActivateInputMethodProperty(key); |
| } |
| |
| void InputMethodManagerImpl::AddInputMethodExtension( |
| const std::string& id, |
| const std::string& name, |
| const std::vector<std::string>& layouts, |
| const std::vector<std::string>& languages, |
| const GURL& options_url, |
| InputMethodEngine* engine) { |
| if (state_ == STATE_TERMINATING) |
| return; |
| |
| if (!extension_ime_util::IsExtensionIME(id) && |
| !ComponentExtensionIMEManager::IsComponentExtensionIMEId(id)) { |
| DVLOG(1) << id << " is not a valid extension input method ID."; |
| return; |
| } |
| |
| extra_input_methods_[id] = |
| InputMethodDescriptor(id, name, layouts, languages, options_url); |
| if (Contains(enabled_extension_imes_, id) && |
| !ComponentExtensionIMEManager::IsComponentExtensionIMEId(id)) { |
| if (!Contains(active_input_method_ids_, id)) { |
| active_input_method_ids_.push_back(id); |
| } else { |
| DVLOG(1) << "AddInputMethodExtension: alread added: " |
| << id << ", " << name; |
| // Call Start() anyway, just in case. |
| } |
| |
| // Ensure that the input method daemon is running. |
| MaybeInitializeCandidateWindowController(); |
| IBusDaemonController::GetInstance()->Start(); |
| } |
| |
| extra_input_method_instances_[id] = |
| static_cast<InputMethodEngineIBus*>(engine); |
| } |
| |
| void InputMethodManagerImpl::RemoveInputMethodExtension(const std::string& id) { |
| if (!extension_ime_util::IsExtensionIME(id)) |
| DVLOG(1) << id << " is not a valid extension input method ID."; |
| |
| std::vector<std::string>::iterator i = std::find( |
| active_input_method_ids_.begin(), active_input_method_ids_.end(), id); |
| if (i != active_input_method_ids_.end()) |
| active_input_method_ids_.erase(i); |
| extra_input_methods_.erase(id); |
| |
| if (ContainsOnlyKeyboardLayout(active_input_method_ids_)) { |
| // Do NOT call ibus_controller_->Stop(); here to work around a crash issue |
| // at crosbug.com/27051. |
| // TODO(yusukes): We can safely call Stop(); here once crosbug.com/26443 |
| // is implemented. |
| } |
| |
| // If |current_input_method| is no longer in |active_input_method_ids_|, |
| // switch to the first one in |active_input_method_ids_|. |
| ChangeInputMethod(current_input_method_.id()); |
| |
| std::map<std::string, InputMethodEngineIBus*>::iterator ite = |
| extra_input_method_instances_.find(id); |
| if (ite == extra_input_method_instances_.end()) { |
| DVLOG(1) << "The engine instance of " << id << " has already gone."; |
| } else { |
| // Do NOT release the actual instance here. This class does not take an |
| // onwership of engine instance. |
| extra_input_method_instances_.erase(ite); |
| } |
| } |
| |
| void InputMethodManagerImpl::GetInputMethodExtensions( |
| InputMethodDescriptors* result) { |
| // Build the extension input method descriptors from the extra input |
| // methods cache |extra_input_methods_|. |
| std::map<std::string, InputMethodDescriptor>::iterator iter; |
| for (iter = extra_input_methods_.begin(); iter != extra_input_methods_.end(); |
| ++iter) { |
| if (extension_ime_util::IsExtensionIME(iter->first)) |
| result->push_back(iter->second); |
| } |
| } |
| |
| void InputMethodManagerImpl::SetEnabledExtensionImes( |
| std::vector<std::string>* ids) { |
| enabled_extension_imes_.clear(); |
| enabled_extension_imes_.insert(enabled_extension_imes_.end(), |
| ids->begin(), |
| ids->end()); |
| |
| bool active_imes_changed = false; |
| |
| for (std::map<std::string, InputMethodDescriptor>::iterator extra_iter = |
| extra_input_methods_.begin(); extra_iter != extra_input_methods_.end(); |
| ++extra_iter) { |
| if (ComponentExtensionIMEManager::IsComponentExtensionIMEId( |
| extra_iter->first)) |
| continue; // Do not filter component extension. |
| std::vector<std::string>::iterator active_iter = std::find( |
| active_input_method_ids_.begin(), active_input_method_ids_.end(), |
| extra_iter->first); |
| |
| bool active = active_iter != active_input_method_ids_.end(); |
| bool enabled = Contains(enabled_extension_imes_, extra_iter->first); |
| |
| if (active && !enabled) |
| active_input_method_ids_.erase(active_iter); |
| |
| if (!active && enabled) |
| active_input_method_ids_.push_back(extra_iter->first); |
| |
| if (active == !enabled) |
| active_imes_changed = true; |
| } |
| |
| if (active_imes_changed) { |
| MaybeInitializeCandidateWindowController(); |
| IBusDaemonController::GetInstance()->Start(); |
| |
| // If |current_input_method| is no longer in |active_input_method_ids_|, |
| // switch to the first one in |active_input_method_ids_|. |
| ChangeInputMethod(current_input_method_.id()); |
| } |
| } |
| |
| bool InputMethodManagerImpl::SwitchToNextInputMethod() { |
| // Sanity checks. |
| if (active_input_method_ids_.empty()) { |
| DVLOG(1) << "active input method is empty"; |
| return false; |
| } |
| if (current_input_method_.id().empty()) { |
| DVLOG(1) << "current_input_method_ is unknown"; |
| return false; |
| } |
| |
| // Do not consume key event if there is only one input method is enabled. |
| // Ctrl+Space or Alt+Shift may be used by other application. |
| if (active_input_method_ids_.size() == 1) |
| return false; |
| |
| // Find the next input method and switch to it. |
| SwitchToNextInputMethodInternal(active_input_method_ids_, |
| current_input_method_.id()); |
| return true; |
| } |
| |
| bool InputMethodManagerImpl::SwitchToPreviousInputMethod( |
| const ui::Accelerator& accelerator) { |
| // Sanity check. |
| if (active_input_method_ids_.empty()) { |
| DVLOG(1) << "active input method is empty"; |
| return false; |
| } |
| |
| // Do not consume key event if there is only one input method is enabled. |
| // Ctrl+Space or Alt+Shift may be used by other application. |
| if (active_input_method_ids_.size() == 1) |
| return false; |
| |
| if (accelerator.type() == ui::ET_KEY_RELEASED) |
| return true; |
| |
| if (previous_input_method_.id().empty() || |
| previous_input_method_.id() == current_input_method_.id()) { |
| return SwitchToNextInputMethod(); |
| } |
| |
| std::vector<std::string>::const_iterator iter = |
| std::find(active_input_method_ids_.begin(), |
| active_input_method_ids_.end(), |
| previous_input_method_.id()); |
| if (iter == active_input_method_ids_.end()) { |
| // previous_input_method_ is not supported. |
| return SwitchToNextInputMethod(); |
| } |
| ChangeInputMethodInternal(*iter, true); |
| return true; |
| } |
| |
| bool InputMethodManagerImpl::SwitchInputMethod( |
| const ui::Accelerator& accelerator) { |
| // Sanity check. |
| if (active_input_method_ids_.empty()) { |
| DVLOG(1) << "active input method is empty"; |
| return false; |
| } |
| |
| // Get the list of input method ids for the |accelerator|. For example, get |
| // { "mozc-hangul", "xkb:kr:kr104:kor" } for ui::VKEY_DBE_SBCSCHAR. |
| std::vector<std::string> input_method_ids_to_switch; |
| switch (accelerator.key_code()) { |
| case ui::VKEY_CONVERT: // Henkan key on JP106 keyboard |
| input_method_ids_to_switch.push_back(nacl_mozc_jp_id); |
| break; |
| case ui::VKEY_NONCONVERT: // Muhenkan key on JP106 keyboard |
| input_method_ids_to_switch.push_back("xkb:jp::jpn"); |
| break; |
| case ui::VKEY_DBE_SBCSCHAR: // ZenkakuHankaku key on JP106 keyboard |
| case ui::VKEY_DBE_DBCSCHAR: |
| input_method_ids_to_switch.push_back(nacl_mozc_jp_id); |
| input_method_ids_to_switch.push_back("xkb:jp::jpn"); |
| break; |
| default: |
| NOTREACHED(); |
| break; |
| } |
| if (input_method_ids_to_switch.empty()) { |
| DVLOG(1) << "Unexpected VKEY: " << accelerator.key_code(); |
| return false; |
| } |
| |
| // Obtain the intersection of input_method_ids_to_switch and |
| // active_input_method_ids_. The order of IDs in active_input_method_ids_ is |
| // preserved. |
| std::vector<std::string> ids; |
| for (size_t i = 0; i < input_method_ids_to_switch.size(); ++i) { |
| const std::string& id = input_method_ids_to_switch[i]; |
| if (Contains(active_input_method_ids_, id)) |
| ids.push_back(id); |
| } |
| if (ids.empty()) { |
| // No input method for the accelerator is active. For example, we should |
| // just ignore VKEY_HANGUL when mozc-hangul is not active. |
| return false; |
| } |
| |
| SwitchToNextInputMethodInternal(ids, current_input_method_.id()); |
| return true; // consume the accelerator. |
| } |
| |
| void InputMethodManagerImpl::SwitchToNextInputMethodInternal( |
| const std::vector<std::string>& input_method_ids, |
| const std::string& current_input_method_id) { |
| std::vector<std::string>::const_iterator iter = |
| std::find(input_method_ids.begin(), |
| input_method_ids.end(), |
| current_input_method_id); |
| if (iter != input_method_ids.end()) |
| ++iter; |
| if (iter == input_method_ids.end()) |
| iter = input_method_ids.begin(); |
| ChangeInputMethodInternal(*iter, true); |
| } |
| |
| InputMethodDescriptor InputMethodManagerImpl::GetCurrentInputMethod() const { |
| if (current_input_method_.id().empty()) |
| return InputMethodUtil::GetFallbackInputMethodDescriptor(); |
| return current_input_method_; |
| } |
| |
| InputMethodPropertyList |
| InputMethodManagerImpl::GetCurrentInputMethodProperties() const { |
| // This check is necessary since an IME property (e.g. for Pinyin) might be |
| // sent from ibus-daemon AFTER the current input method is switched to XKB. |
| if (InputMethodUtil::IsKeyboardLayout(GetCurrentInputMethod().id())) |
| return InputMethodPropertyList(); |
| return ibus_controller_->GetCurrentProperties(); |
| } |
| |
| XKeyboard* InputMethodManagerImpl::GetXKeyboard() { |
| return xkeyboard_.get(); |
| } |
| |
| InputMethodUtil* InputMethodManagerImpl::GetInputMethodUtil() { |
| return &util_; |
| } |
| |
| ComponentExtensionIMEManager* |
| InputMethodManagerImpl::GetComponentExtensionIMEManager() { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| return component_extension_ime_manager_.get(); |
| } |
| |
| void InputMethodManagerImpl::OnConnected() { |
| for (std::map<std::string, InputMethodEngineIBus*>::iterator ite = |
| extra_input_method_instances_.begin(); |
| ite != extra_input_method_instances_.end(); |
| ite++) { |
| if (Contains(enabled_extension_imes_, ite->first) || |
| (component_extension_ime_manager_->IsInitialized() && |
| component_extension_ime_manager_->IsWhitelisted(ite->first))) { |
| ite->second->OnConnected(); |
| } |
| } |
| |
| if (!pending_input_method_.empty()) |
| ChangeInputMethodInternal(pending_input_method_, false); |
| } |
| |
| void InputMethodManagerImpl::OnDisconnected() { |
| for (std::map<std::string, InputMethodEngineIBus*>::iterator ite = |
| extra_input_method_instances_.begin(); |
| ite != extra_input_method_instances_.end(); |
| ite++) { |
| if (Contains(enabled_extension_imes_, ite->first)) |
| ite->second->OnDisconnected(); |
| } |
| } |
| |
| void InputMethodManagerImpl::InitializeComponentExtension() { |
| ComponentExtensionIMEManagerImpl* impl = |
| new ComponentExtensionIMEManagerImpl(); |
| scoped_ptr<ComponentExtensionIMEManagerDelegate> delegate(impl); |
| impl->InitializeAsync(base::Bind( |
| &InputMethodManagerImpl::OnComponentExtensionInitialized, |
| weak_ptr_factory_.GetWeakPtr(), |
| base::Passed(&delegate))); |
| } |
| |
| void InputMethodManagerImpl::Init(base::SequencedTaskRunner* ui_task_runner) { |
| DCHECK(!ibus_controller_.get()); |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| |
| ibus_controller_.reset(IBusController::Create()); |
| xkeyboard_.reset(XKeyboard::Create()); |
| ibus_controller_->AddObserver(this); |
| |
| // We can't call impl->Initialize here, because file thread is not available |
| // at this moment. |
| ui_task_runner->PostTask( |
| FROM_HERE, |
| base::Bind(&InputMethodManagerImpl::InitializeComponentExtension, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| |
| void InputMethodManagerImpl::SetIBusControllerForTesting( |
| IBusController* ibus_controller) { |
| ibus_controller_.reset(ibus_controller); |
| ibus_controller_->AddObserver(this); |
| } |
| |
| void InputMethodManagerImpl::SetCandidateWindowControllerForTesting( |
| CandidateWindowController* candidate_window_controller) { |
| candidate_window_controller_.reset(candidate_window_controller); |
| candidate_window_controller_->Init(); |
| candidate_window_controller_->AddObserver(this); |
| } |
| |
| void InputMethodManagerImpl::SetXKeyboardForTesting(XKeyboard* xkeyboard) { |
| xkeyboard_.reset(xkeyboard); |
| } |
| |
| void InputMethodManagerImpl::InitializeComponentExtensionForTesting( |
| scoped_ptr<ComponentExtensionIMEManagerDelegate> delegate) { |
| OnComponentExtensionInitialized(delegate.Pass()); |
| } |
| |
| void InputMethodManagerImpl::PropertyChanged() { |
| FOR_EACH_OBSERVER(InputMethodManager::Observer, |
| observers_, |
| InputMethodPropertyChanged(this)); |
| } |
| |
| void InputMethodManagerImpl::CandidateWindowOpened() { |
| FOR_EACH_OBSERVER(InputMethodManager::CandidateWindowObserver, |
| candidate_window_observers_, |
| CandidateWindowOpened(this)); |
| } |
| |
| void InputMethodManagerImpl::CandidateWindowClosed() { |
| FOR_EACH_OBSERVER(InputMethodManager::CandidateWindowObserver, |
| candidate_window_observers_, |
| CandidateWindowClosed(this)); |
| } |
| |
| void InputMethodManagerImpl::OnScreenLocked() { |
| saved_previous_input_method_ = previous_input_method_; |
| saved_current_input_method_ = current_input_method_; |
| saved_active_input_method_ids_ = active_input_method_ids_; |
| |
| const std::string hardware_keyboard_id = util_.GetHardwareInputMethodId(); |
| // We'll add the hardware keyboard if it's not included in |
| // |active_input_method_list| so that the user can always use the hardware |
| // keyboard on the screen locker. |
| bool should_add_hardware_keyboard = true; |
| |
| active_input_method_ids_.clear(); |
| for (size_t i = 0; i < saved_active_input_method_ids_.size(); ++i) { |
| const std::string& input_method_id = saved_active_input_method_ids_[i]; |
| // Skip if it's not a keyboard layout. Drop input methods including |
| // extension ones. |
| if (!InputMethodUtil::IsKeyboardLayout(input_method_id)) |
| continue; |
| active_input_method_ids_.push_back(input_method_id); |
| if (input_method_id == hardware_keyboard_id) |
| should_add_hardware_keyboard = false; |
| } |
| if (should_add_hardware_keyboard) |
| active_input_method_ids_.push_back(hardware_keyboard_id); |
| |
| ChangeInputMethod(current_input_method_.id()); |
| } |
| |
| void InputMethodManagerImpl::OnScreenUnlocked() { |
| previous_input_method_ = saved_previous_input_method_; |
| current_input_method_ = saved_current_input_method_; |
| active_input_method_ids_ = saved_active_input_method_ids_; |
| |
| ChangeInputMethod(current_input_method_.id()); |
| } |
| |
| bool InputMethodManagerImpl::InputMethodIsActivated( |
| const std::string& input_method_id) { |
| return Contains(active_input_method_ids_, input_method_id); |
| } |
| |
| bool InputMethodManagerImpl::ContainsOnlyKeyboardLayout( |
| const std::vector<std::string>& value) { |
| for (size_t i = 0; i < value.size(); ++i) { |
| if (!InputMethodUtil::IsKeyboardLayout(value[i])) |
| return false; |
| } |
| return true; |
| } |
| |
| void InputMethodManagerImpl::MaybeInitializeCandidateWindowController() { |
| if (candidate_window_controller_.get()) |
| return; |
| |
| candidate_window_controller_.reset( |
| CandidateWindowController::CreateCandidateWindowController()); |
| if (candidate_window_controller_->Init()) |
| candidate_window_controller_->AddObserver(this); |
| else |
| DVLOG(1) << "Failed to initialize the candidate window controller"; |
| } |
| |
| bool InputMethodManagerImpl::IsIBusConnectionAlive() { |
| return DBusThreadManager::Get() && DBusThreadManager::Get()->GetIBusClient(); |
| } |
| |
| } // namespace input_method |
| } // namespace chromeos |