input_method_util.cc revision e5d81f57cb97b3b6b7fccc9c5610d21eb81db09d
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_util.h" 6 7#include <algorithm> 8#include <functional> 9#include <map> 10#include <utility> 11 12#include "base/basictypes.h" 13#include "base/memory/scoped_ptr.h" 14#include "base/prefs/pref_service.h" 15#include "base/strings/string_split.h" 16#include "base/strings/string_util.h" 17#include "base/strings/utf_string_conversions.h" 18#include "chromeos/ime/component_extension_ime_manager.h" 19#include "chromeos/ime/extension_ime_util.h" 20// For SetHardwareKeyboardLayoutForTesting. 21#include "chromeos/ime/fake_input_method_delegate.h" 22#include "chromeos/ime/input_method_delegate.h" 23#include "chromeos/ime/input_method_whitelist.h" 24// TODO(nona): move this header from this file. 25#include "grit/generated_resources.h" 26 27namespace { 28 29// A mapping from an input method id to a string for the language indicator. The 30// mapping is necessary since some input methods belong to the same language. 31// For example, both "xkb:us::eng" and "xkb:us:dvorak:eng" are for US English. 32const struct { 33 const char* input_method_id; 34 const char* indicator_text; 35} kMappingFromIdToIndicatorText[] = { 36 // To distinguish from "xkb:jp::jpn" 37 // TODO(nona): Make following variables configurable. http://crbug.com/232260. 38 { "_comp_ime_fpfbhcjppmaeaijcidgiibchfbnhbeljnacl_mozc_us", "\xe3\x81\x82" }, 39 { "_comp_ime_fpfbhcjppmaeaijcidgiibchfbnhbeljnacl_mozc_jp", "\xe3\x81\x82" }, 40 { "_comp_ime_bbaiamgfapehflhememkfglaehiobjnknacl_mozc_us", "\xe3\x81\x82" }, 41 { "_comp_ime_bbaiamgfapehflhememkfglaehiobjnknacl_mozc_jp", "\xe3\x81\x82" }, 42 // For simplified Chinese input methods 43 { "pinyin", "\xe6\x8b\xbc" }, // U+62FC 44 { "_comp_ime_cpgalbafkoofkjmaeonnfijgpfennjjnzh-t-i0-pinyin", 45 "\xe6\x8b\xbc" }, 46 { "_comp_ime_gjaehgfemfahhmlgpdfknkhdnemmolopzh-t-i0-pinyin", 47 "\xe6\x8b\xbc" }, 48 { "_comp_ime_gjaehgfemfahhmlgpdfknkhdnemmolopzh-t-i0-wubi-1986", 49 "\xe4\xba\x94" }, // U+4E94 50 { "pinyin-dv", "\xe6\x8b\xbc" }, 51 // For traditional Chinese input methods 52 { "mozc-chewing", "\xe9\x85\xb7" }, // U+9177 53 { "_comp_ime_ekbifjdfhkmdeeajnolmgdlmkllopefizh-hant-t-i0-und", 54 "\xE6\xB3\xA8" }, // U+6CE8 55 { "_comp_ime_gjaehgfemfahhmlgpdfknkhdnemmolopzh-hant-t-i0-und", 56 "\xE6\xB3\xA8" }, // U+6CE8 57 { "m17n:zh:cangjie", "\xe5\x80\x89" }, // U+5009 58 { "_comp_ime_aeebooiibjahgpgmhkeocbeekccfknbjzh-hant-t-i0-cangjie-1987", 59 "\xe5\x80\x89" }, // U+5009 60 { "_comp_ime_gjaehgfemfahhmlgpdfknkhdnemmolopzh-hant-t-i0-cangjie-1987", 61 "\xe5\x80\x89" }, // U+5009 62 { "m17n:zh:quick", "\xe9\x80\x9f" }, // U+901F 63 // For Hangul input method. 64 { "mozc-hangul", "\xed\x95\x9c" }, // U+D55C 65 { "_comp_ime_bdgdidmhaijohebebipajioienkglgfohangul_2set", "\xed\x95\x9c" }, 66 { "_comp_ime_bdgdidmhaijohebebipajioienkglgfohangul_3set390", 67 "\xed\x95\x9c" }, 68 { "_comp_ime_bdgdidmhaijohebebipajioienkglgfohangul_3setfinal", 69 "\xed\x95\x9c" }, 70 { "_comp_ime_bdgdidmhaijohebebipajioienkglgfohangul_3setnoshift", 71 "\xed\x95\x9c" }, 72 { "_comp_ime_bdgdidmhaijohebebipajioienkglgfohangul_romaja", "\xed\x95\x9c" }, 73}; 74 75const size_t kMappingFromIdToIndicatorTextLen = 76 ARRAYSIZE_UNSAFE(kMappingFromIdToIndicatorText); 77 78// A mapping from an input method id to a resource id for a 79// medium length language indicator. 80// For those languages that want to display a slightly longer text in the 81// "Your input method has changed to..." bubble than in the status tray. 82// If an entry is not found in this table the short name is used. 83const struct { 84 const char* input_method_id; 85 const int resource_id; 86} kMappingImeIdToMediumLenNameResourceId[] = { 87 { "m17n:zh:cangjie", IDS_LANGUAGES_MEDIUM_LEN_NAME_CHINESE_TRADITIONAL }, 88 { "m17n:zh:quick", IDS_LANGUAGES_MEDIUM_LEN_NAME_CHINESE_TRADITIONAL }, 89 { "mozc-chewing", IDS_LANGUAGES_MEDIUM_LEN_NAME_CHINESE_TRADITIONAL }, 90 { "mozc-hangul", IDS_LANGUAGES_MEDIUM_LEN_NAME_KOREAN }, 91 { "pinyin", IDS_LANGUAGES_MEDIUM_LEN_NAME_CHINESE_SIMPLIFIED }, 92 { "pinyin-dv", IDS_LANGUAGES_MEDIUM_LEN_NAME_CHINESE_SIMPLIFIED }, 93 { "_comp_ime_cpgalbafkoofkjmaeonnfijgpfennjjnzh-t-i0-pinyin", 94 IDS_LANGUAGES_MEDIUM_LEN_NAME_CHINESE_SIMPLIFIED}, 95 { "_comp_ime_gjaehgfemfahhmlgpdfknkhdnemmolopzh-t-i0-pinyin", 96 IDS_LANGUAGES_MEDIUM_LEN_NAME_CHINESE_SIMPLIFIED }, 97 { "_comp_ime_gjaehgfemfahhmlgpdfknkhdnemmolopzh-t-i0-wubi-1986", 98 IDS_LANGUAGES_MEDIUM_LEN_NAME_CHINESE_SIMPLIFIED }, 99 { "_comp_ime_ekbifjdfhkmdeeajnolmgdlmkllopefizh-hant-t-i0-und", 100 IDS_LANGUAGES_MEDIUM_LEN_NAME_CHINESE_TRADITIONAL }, 101 { "_comp_ime_gjaehgfemfahhmlgpdfknkhdnemmolopzh-hant-t-i0-und", 102 IDS_LANGUAGES_MEDIUM_LEN_NAME_CHINESE_TRADITIONAL }, 103 { "_comp_ime_aeebooiibjahgpgmhkeocbeekccfknbjzh-hant-t-i0-cangjie-1987", 104 IDS_LANGUAGES_MEDIUM_LEN_NAME_CHINESE_TRADITIONAL }, 105 { "_comp_ime_gjaehgfemfahhmlgpdfknkhdnemmolopzh-hant-t-i0-cangjie-1987", 106 IDS_LANGUAGES_MEDIUM_LEN_NAME_CHINESE_TRADITIONAL }, 107}; 108const size_t kMappingImeIdToMediumLenNameResourceIdLen = 109 ARRAYSIZE_UNSAFE(kMappingImeIdToMediumLenNameResourceId); 110 111// Due to asynchronous initialization of component extension manager, 112// GetFirstLogingInputMethodIds may miss component extension IMEs. To enable 113// component extension IME as the first loging input method, we have to prepare 114// component extension IME IDs. 115const struct { 116 const char* locale; 117 const char* layout; 118 const char* input_method_id; 119} kDefaultInputMethodRecommendation[] = { 120 { "ja", "us", "_comp_ime_fpfbhcjppmaeaijcidgiibchfbnhbeljnacl_mozc_us" }, 121 { "ja", "jp", "_comp_ime_fpfbhcjppmaeaijcidgiibchfbnhbeljnacl_mozc_jp" }, 122 { "zh-CN", "us", "_comp_ime_gjaehgfemfahhmlgpdfknkhdnemmolopzh-t-i0-pinyin" }, 123 { "zh-TW", "us", 124 "_comp_ime_gjaehgfemfahhmlgpdfknkhdnemmolopzh-hant-t-i0-und" }, 125#if defined(OFFICIAL_BUILD) 126 { "th", "us", "_comp_ime_habcdindjejkmepknlhkkloncjcpcnbfvkd_th" }, 127 { "vi", "us", "_comp_ime_habcdindjejkmepknlhkkloncjcpcnbfvkd_vi_tcvn" }, 128 { "vi", "us", "_comp_ime_habcdindjejkmepknlhkkloncjcpcnbfvkd_vi_tcvn" }, 129#else 130 { "th", "us", "_comp_ime_jhffeifommiaekmbkkjlpmilogcfdohpvkd_th" }, 131 { "vi", "us", "_comp_ime_jhffeifommiaekmbkkjlpmilogcfdohpvkd_vi_tcvn" }, 132 { "vi", "us", "_comp_ime_jhffeifommiaekmbkkjlpmilogcfdohpvkd_vi_tcvn" }, 133#endif 134}; 135 136// The map from xkb layout to the indicator text. 137// Refer to crbug.com/349829. 138const char* const kXkbIndicators[][2] = {{"am", "AM"}, 139 {"be", "BE"}, 140 {"bg", "BG"}, 141 {"bg(phonetic)", "BG"}, 142 {"br", "BR"}, 143 {"by", "BY"}, 144 {"ca", "CA"}, 145 {"ca(eng)", "CA"}, 146 {"ca(multix)", "CA"}, 147 {"ch", "CH"}, 148 {"ch(fr)", "CH"}, 149 {"cz", "CZ"}, 150 {"cz(qwerty)", "CS"}, 151 {"de", "DE"}, 152 {"de(neo)", "NEO"}, 153 {"dk", "DK"}, 154 {"ee", "EE"}, 155 {"es", "ES"}, 156 {"es(cat)", "CAS"}, 157 {"fi", "FI"}, 158 {"fr", "FR"}, 159 {"gb(dvorak)", "DV"}, 160 {"gb(extd)", "GB"}, 161 {"ge", "GE"}, 162 {"gr", "GR"}, 163 {"hr", "HR"}, 164 {"hu", "HU"}, 165 {"il", "IL"}, 166 {"is", "IS"}, 167 {"it", "IT"}, 168 {"jp", "JA"}, 169 {"latam", "LA"}, 170 {"lt", "LT"}, 171 {"lv(apostrophe)", "LV"}, 172 {"mn", "MN"}, 173 {"no", "NO"}, 174 {"pl", "PL"}, 175 {"pt", "PT"}, 176 {"ro", "RO"}, 177 {"rs", "RS"}, 178 {"ru", "RU"}, 179 {"ru(phonetic)", "RU"}, 180 {"se", "SE"}, 181 {"si", "SI"}, 182 {"sk", "SK"}, 183 {"tr", "TR"}, 184 {"ua", "UA"}, 185 {"us", "US"}, 186 {"us(altgr-intl)", "EXTD"}, 187 {"us(colemak)", "CO"}, 188 {"us(dvorak)", "DV"}, 189 {"us(intl)", "INTL"}, }; 190 191// The old chinese input method ids for migration. 192// See crbug.com/357384. 193const char* kOldChineseExtensionIds[] = { 194 "goedamlknlnjaengojinmfgpmdjmkooo", 195 "nmblnjkfdkabgdofidlkienfnnbjhnab", 196 "gjhclobljhjhgoebiipblnmdodbmpdgd" 197}; 198 199// The new chinese input method id for migration. 200// See crbug.com/357384. 201const char* kNewChineseExtensionId = "gjaehgfemfahhmlgpdfknkhdnemmolop"; 202 203const size_t kExtensionIdLen = 32; 204 205} // namespace 206 207namespace chromeos { 208 209extern const char* kExtensionImePrefix; 210 211namespace input_method { 212 213namespace { 214 215const struct EnglishToResouceId { 216 const char* english_string_from_ibus; 217 int resource_id; 218} kEnglishToResourceIdArray[] = { 219 // For ibus-mozc-hangul 220 { "Hanja mode", IDS_STATUSBAR_IME_KOREAN_HANJA_INPUT_MODE }, 221 { "Hangul mode", IDS_STATUSBAR_IME_KOREAN_HANGUL_INPUT_MODE }, 222 223 // For ibus-mozc-pinyin. 224 { "Full/Half width", 225 IDS_STATUSBAR_IME_CHINESE_PINYIN_TOGGLE_FULL_HALF }, 226 { "Full/Half width punctuation", 227 IDS_STATUSBAR_IME_CHINESE_PINYIN_TOGGLE_FULL_HALF_PUNCTUATION }, 228 // TODO(hsumita): Fixes a typo 229 { "Simplfied/Traditional Chinese", 230 IDS_STATUSBAR_IME_CHINESE_PINYIN_TOGGLE_S_T_CHINESE }, 231 { "Chinese", 232 IDS_STATUSBAR_IME_CHINESE_PINYIN_TOGGLE_CHINESE_ENGLISH }, 233 234 // For ibus-mozc-chewing. 235 { "English", 236 IDS_STATUSBAR_IME_CHINESE_MOZC_CHEWING_ENGLISH_MODE }, 237 { "_Chinese", 238 IDS_STATUSBAR_IME_CHINESE_MOZC_CHEWING_CHINESE_MODE }, 239 { "Full-width English", 240 IDS_STATUSBAR_IME_CHINESE_MOZC_CHEWING_FULL_WIDTH_ENGLISH_MODE }, 241 242 // For the "Languages and Input" dialog. 243 { "m17n:ar:kbd", IDS_OPTIONS_SETTINGS_LANGUAGES_M17N_STANDARD_INPUT_METHOD }, 244 { "m17n:hi:itrans", // also uses the "STANDARD_INPUT_METHOD" id. 245 IDS_OPTIONS_SETTINGS_LANGUAGES_M17N_STANDARD_INPUT_METHOD }, 246 { "m17n:zh:cangjie", 247 IDS_OPTIONS_SETTINGS_LANGUAGES_M17N_CHINESE_CANGJIE_INPUT_METHOD }, 248 { "m17n:zh:quick", 249 IDS_OPTIONS_SETTINGS_LANGUAGES_M17N_CHINESE_QUICK_INPUT_METHOD }, 250 { "m17n:fa:isiri", 251 IDS_OPTIONS_SETTINGS_LANGUAGES_M17N_PERSIAN_ISIRI_2901_INPUT_METHOD }, 252 { "m17n:th:kesmanee", 253 IDS_OPTIONS_SETTINGS_LANGUAGES_M17N_THAI_KESMANEE_INPUT_METHOD }, 254 { "m17n:th:tis820", 255 IDS_OPTIONS_SETTINGS_LANGUAGES_M17N_THAI_TIS820_INPUT_METHOD }, 256 { "m17n:th:pattachote", 257 IDS_OPTIONS_SETTINGS_LANGUAGES_M17N_THAI_PATTACHOTE_INPUT_METHOD }, 258 { "m17n:vi:tcvn", 259 IDS_OPTIONS_SETTINGS_LANGUAGES_M17N_VIETNAMESE_TCVN_INPUT_METHOD }, 260 { "m17n:vi:telex", 261 IDS_OPTIONS_SETTINGS_LANGUAGES_M17N_VIETNAMESE_TELEX_INPUT_METHOD }, 262 { "m17n:vi:viqr", 263 IDS_OPTIONS_SETTINGS_LANGUAGES_M17N_VIETNAMESE_VIQR_INPUT_METHOD }, 264 { "m17n:vi:vni", 265 IDS_OPTIONS_SETTINGS_LANGUAGES_M17N_VIETNAMESE_VNI_INPUT_METHOD }, 266 { "m17n:bn:itrans", 267 IDS_OPTIONS_SETTINGS_LANGUAGES_M17N_STANDARD_INPUT_METHOD }, 268 { "m17n:gu:itrans", 269 IDS_OPTIONS_SETTINGS_LANGUAGES_M17N_STANDARD_INPUT_METHOD }, 270 { "m17n:ml:itrans", 271 IDS_OPTIONS_SETTINGS_LANGUAGES_M17N_STANDARD_INPUT_METHOD }, 272 { "m17n:mr:itrans", 273 IDS_OPTIONS_SETTINGS_LANGUAGES_M17N_STANDARD_INPUT_METHOD }, 274 { "m17n:ta:phonetic", 275 IDS_OPTIONS_SETTINGS_LANGUAGES_M17N_TAMIL_PHONETIC }, 276 { "m17n:ta:inscript", 277 IDS_OPTIONS_SETTINGS_LANGUAGES_M17N_TAMIL_INSCRIPT }, 278 { "m17n:ta:tamil99", 279 IDS_OPTIONS_SETTINGS_LANGUAGES_M17N_TAMIL_TAMIL99 }, 280 { "m17n:ta:itrans", 281 IDS_OPTIONS_SETTINGS_LANGUAGES_M17N_TAMIL_ITRANS }, 282 { "m17n:ta:typewriter", 283 IDS_OPTIONS_SETTINGS_LANGUAGES_M17N_TAMIL_TYPEWRITER }, 284 { "m17n:am:sera", 285 IDS_OPTIONS_SETTINGS_LANGUAGES_M17N_STANDARD_INPUT_METHOD }, 286 { "m17n:te:itrans", 287 IDS_OPTIONS_SETTINGS_LANGUAGES_M17N_STANDARD_INPUT_METHOD }, 288 { "m17n:kn:itrans", 289 IDS_OPTIONS_SETTINGS_LANGUAGES_M17N_STANDARD_INPUT_METHOD }, 290 291 { "mozc-chewing", 292 IDS_OPTIONS_SETTINGS_LANGUAGES_CHEWING_INPUT_METHOD }, 293 { "pinyin", IDS_OPTIONS_SETTINGS_LANGUAGES_PINYIN_INPUT_METHOD }, 294 { "pinyin-dv", 295 IDS_OPTIONS_SETTINGS_LANGUAGES_PINYIN_DV_INPUT_METHOD }, 296 { "zinnia-japanese", 297 IDS_OPTIONS_SETTINGS_LANGUAGES_JAPANESE_HANDWRITING_INPUT_METHOD }, 298 { "mozc-hangul", IDS_OPTIONS_SETTINGS_LANGUAGES_KOREAN_INPUT_METHOD }, 299 300 // For ibus-xkb-layouts engine: third_party/ibus-xkb-layouts/files 301 { "xkb:jp::jpn", IDS_STATUSBAR_LAYOUT_JAPAN }, 302 { "xkb:si::slv", IDS_STATUSBAR_LAYOUT_SLOVENIA }, 303 { "xkb:de::ger", IDS_STATUSBAR_LAYOUT_GERMANY }, 304 { "xkb:de:neo:ger", IDS_STATUSBAR_LAYOUT_GERMANY_NEO2 }, 305 { "xkb:it::ita", IDS_STATUSBAR_LAYOUT_ITALY }, 306 { "xkb:ee::est", IDS_STATUSBAR_LAYOUT_ESTONIA }, 307 { "xkb:hu::hun", IDS_STATUSBAR_LAYOUT_HUNGARY }, 308 { "xkb:pl::pol", IDS_STATUSBAR_LAYOUT_POLAND }, 309 { "xkb:dk::dan", IDS_STATUSBAR_LAYOUT_DENMARK }, 310 { "xkb:hr::scr", IDS_STATUSBAR_LAYOUT_CROATIA }, 311 { "xkb:br::por", IDS_STATUSBAR_LAYOUT_BRAZIL }, 312 { "xkb:rs::srp", IDS_STATUSBAR_LAYOUT_SERBIA }, 313 { "xkb:cz::cze", IDS_STATUSBAR_LAYOUT_CZECHIA }, 314 { "xkb:cz:qwerty:cze", IDS_STATUSBAR_LAYOUT_CZECHIA_QWERTY }, 315 { "xkb:us:dvorak:eng", IDS_STATUSBAR_LAYOUT_USA_DVORAK }, 316 { "xkb:us:colemak:eng", IDS_STATUSBAR_LAYOUT_USA_COLEMAK }, 317 { "xkb:ro::rum", IDS_STATUSBAR_LAYOUT_ROMANIA }, 318 { "xkb:us::eng", IDS_STATUSBAR_LAYOUT_USA }, 319 { "xkb:us:altgr-intl:eng", IDS_STATUSBAR_LAYOUT_USA_EXTENDED }, 320 { "xkb:us:intl:eng", IDS_STATUSBAR_LAYOUT_USA_INTERNATIONAL }, 321 { "xkb:lt::lit", IDS_STATUSBAR_LAYOUT_LITHUANIA }, 322 { "xkb:gb:extd:eng", IDS_STATUSBAR_LAYOUT_UNITED_KINGDOM }, 323 { "xkb:gb:dvorak:eng", IDS_STATUSBAR_LAYOUT_UNITED_KINGDOM_DVORAK }, 324 { "xkb:sk::slo", IDS_STATUSBAR_LAYOUT_SLOVAKIA }, 325 { "xkb:ru::rus", IDS_STATUSBAR_LAYOUT_RUSSIA }, 326 { "xkb:ru:phonetic:rus", IDS_STATUSBAR_LAYOUT_RUSSIA_PHONETIC }, 327 { "xkb:gr::gre", IDS_STATUSBAR_LAYOUT_GREECE }, 328 { "xkb:be::fra", IDS_STATUSBAR_LAYOUT_BELGIUM }, 329 { "xkb:be::ger", IDS_STATUSBAR_LAYOUT_BELGIUM }, 330 { "xkb:be::nld", IDS_STATUSBAR_LAYOUT_BELGIUM }, 331 { "xkb:bg::bul", IDS_STATUSBAR_LAYOUT_BULGARIA }, 332 { "xkb:bg:phonetic:bul", IDS_STATUSBAR_LAYOUT_BULGARIA_PHONETIC }, 333 { "xkb:ch::ger", IDS_STATUSBAR_LAYOUT_SWITZERLAND }, 334 { "xkb:ch:fr:fra", IDS_STATUSBAR_LAYOUT_SWITZERLAND_FRENCH }, 335 { "xkb:tr::tur", IDS_STATUSBAR_LAYOUT_TURKEY }, 336 { "xkb:pt::por", IDS_STATUSBAR_LAYOUT_PORTUGAL }, 337 { "xkb:es::spa", IDS_STATUSBAR_LAYOUT_SPAIN }, 338 { "xkb:fi::fin", IDS_STATUSBAR_LAYOUT_FINLAND }, 339 { "xkb:ua::ukr", IDS_STATUSBAR_LAYOUT_UKRAINE }, 340 { "xkb:es:cat:cat", IDS_STATUSBAR_LAYOUT_SPAIN_CATALAN }, 341 { "xkb:fr::fra", IDS_STATUSBAR_LAYOUT_FRANCE }, 342 { "xkb:no::nob", IDS_STATUSBAR_LAYOUT_NORWAY }, 343 { "xkb:se::swe", IDS_STATUSBAR_LAYOUT_SWEDEN }, 344 { "xkb:nl::nld", IDS_STATUSBAR_LAYOUT_NETHERLANDS }, 345 { "xkb:latam::spa", IDS_STATUSBAR_LAYOUT_LATIN_AMERICAN }, 346 { "xkb:lv:apostrophe:lav", IDS_STATUSBAR_LAYOUT_LATVIA }, 347 { "xkb:ca::fra", IDS_STATUSBAR_LAYOUT_CANADA }, 348 { "xkb:ca:eng:eng", IDS_STATUSBAR_LAYOUT_CANADA_ENGLISH }, 349 { "xkb:il::heb", IDS_STATUSBAR_LAYOUT_ISRAEL }, 350 { "xkb:kr:kr104:kor", IDS_STATUSBAR_LAYOUT_KOREA_104 }, 351 { "xkb:is::ice", IDS_STATUSBAR_LAYOUT_ICELANDIC }, 352 { "xkb:ca:multix:fra", IDS_STATUSBAR_LAYOUT_CANADIAN_MULTILINGUAL }, 353 { "xkb:by::bel", IDS_STATUSBAR_LAYOUT_BELARUSIAN }, 354 { "xkb:am:phonetic:arm", IDS_STATUSBAR_LAYOUT_ARMENIAN_PHONETIC }, 355 { "xkb:ge::geo", IDS_STATUSBAR_LAYOUT_GEORGIAN }, 356 { "xkb:mn::mon", IDS_STATUSBAR_LAYOUT_MONGOLIAN }, 357 358 { "english-m", IDS_STATUSBAR_LAYOUT_USA_MYSTERY }, 359}; 360const size_t kEnglishToResourceIdArraySize = 361 arraysize(kEnglishToResourceIdArray); 362 363} // namespace 364 365InputMethodUtil::InputMethodUtil( 366 InputMethodDelegate* delegate, 367 scoped_ptr<InputMethodDescriptors> supported_input_methods) 368 : supported_input_methods_(supported_input_methods.Pass()), 369 delegate_(delegate) { 370 // Makes sure the supported input methods at least have the fallback ime. 371 // So that it won't cause massive test failures. 372 if (supported_input_methods_->empty()) 373 supported_input_methods_->push_back(GetFallbackInputMethodDescriptor()); 374 375 ReloadInternalMaps(); 376 377 // Initialize a map from English string to Chrome string resource ID as well. 378 for (size_t i = 0; i < kEnglishToResourceIdArraySize; ++i) { 379 const EnglishToResouceId& map_entry = kEnglishToResourceIdArray[i]; 380 const bool result = english_to_resource_id_.insert(std::make_pair( 381 map_entry.english_string_from_ibus, map_entry.resource_id)).second; 382 DCHECK(result) << "Duplicated string is found: " 383 << map_entry.english_string_from_ibus; 384 } 385 386 // Initialize the map from xkb layout to indicator text. 387 for (size_t i = 0; i < arraysize(kXkbIndicators); ++i) { 388 xkb_layout_to_indicator_[kXkbIndicators[i][0]] = kXkbIndicators[i][1]; 389 } 390} 391 392InputMethodUtil::~InputMethodUtil() { 393} 394 395bool InputMethodUtil::TranslateStringInternal( 396 const std::string& english_string, base::string16 *out_string) const { 397 DCHECK(out_string); 398 HashType::const_iterator iter = english_to_resource_id_.find(english_string); 399 if (iter == english_to_resource_id_.end()) { 400 // TODO(yusukes): Write Autotest which checks if all display names and all 401 // property names for supported input methods are listed in the resource 402 // ID array (crosbug.com/4572). 403 LOG(ERROR) << "Resource ID is not found for: " << english_string; 404 return false; 405 } 406 407 *out_string = delegate_->GetLocalizedString(iter->second); 408 return true; 409} 410 411base::string16 InputMethodUtil::TranslateString( 412 const std::string& english_string) const { 413 base::string16 localized_string; 414 if (TranslateStringInternal(english_string, &localized_string)) { 415 return localized_string; 416 } 417 return base::UTF8ToUTF16(english_string); 418} 419 420bool InputMethodUtil::IsValidInputMethodId( 421 const std::string& input_method_id) const { 422 // We can't check the component extension is whilelisted or not here because 423 // it might not be initialized. 424 return GetInputMethodDescriptorFromId(input_method_id) != NULL || 425 extension_ime_util::IsComponentExtensionIME(input_method_id); 426} 427 428// static 429bool InputMethodUtil::IsKeyboardLayout(const std::string& input_method_id) { 430 return StartsWithASCII(input_method_id, "xkb:", false) || 431 extension_ime_util::IsKeyboardLayoutExtension(input_method_id); 432} 433 434std::string InputMethodUtil::GetLanguageCodeFromInputMethodId( 435 const std::string& input_method_id) const { 436 // The code should be compatible with one of codes used for UI languages, 437 // defined in app/l10_util.cc. 438 const char kDefaultLanguageCode[] = "en-US"; 439 std::map<std::string, std::string>::const_iterator iter 440 = id_to_language_code_.find(input_method_id); 441 return (iter == id_to_language_code_.end()) ? 442 // Returning |kDefaultLanguageCode| here is not for Chrome OS but for 443 // Ubuntu where the ibus-xkb-layouts engine could be missing. 444 kDefaultLanguageCode : iter->second; 445} 446 447std::string InputMethodUtil::GetKeyboardLayoutName( 448 const std::string& input_method_id) const { 449 InputMethodIdToDescriptorMap::const_iterator iter 450 = id_to_descriptor_.find(input_method_id); 451 return (iter == id_to_descriptor_.end()) ? 452 "" : iter->second.GetPreferredKeyboardLayout(); 453} 454 455std::string InputMethodUtil::GetInputMethodDisplayNameFromId( 456 const std::string& input_method_id) const { 457 base::string16 display_name; 458 if (!extension_ime_util::IsExtensionIME(input_method_id) && 459 TranslateStringInternal(input_method_id, &display_name)) { 460 return base::UTF16ToUTF8(display_name); 461 } 462 // Return an empty string if the display name is not found. 463 return ""; 464} 465 466base::string16 InputMethodUtil::GetInputMethodShortName( 467 const InputMethodDescriptor& input_method) const { 468 // For the status area, we use two-letter, upper-case language code like 469 // "US" and "JP". 470 471 // Use the indicator string if set. 472 if (!input_method.indicator().empty()) { 473 return base::UTF8ToUTF16(input_method.indicator()); 474 } 475 476 base::string16 text; 477 // Check special cases first. 478 for (size_t i = 0; i < kMappingFromIdToIndicatorTextLen; ++i) { 479 if (kMappingFromIdToIndicatorText[i].input_method_id == 480 input_method.id()) { 481 text = base::UTF8ToUTF16(kMappingFromIdToIndicatorText[i].indicator_text); 482 break; 483 } 484 } 485 486 // Display the keyboard layout name when using a keyboard layout. 487 if (text.empty() && IsKeyboardLayout(input_method.id())) { 488 std::map<std::string, std::string>::const_iterator it = 489 xkb_layout_to_indicator_.find(GetKeyboardLayoutName(input_method.id())); 490 if (it != xkb_layout_to_indicator_.end()) 491 text = base::UTF8ToUTF16(it->second); 492 } 493 494 // TODO(yusukes): Some languages have two or more input methods. For example, 495 // Thai has 3, Vietnamese has 4. If these input methods could be activated at 496 // the same time, we should do either of the following: 497 // (1) Add mappings to |kMappingFromIdToIndicatorText| 498 // (2) Add suffix (1, 2, ...) to |text| when ambiguous. 499 500 if (text.empty()) { 501 const size_t kMaxLanguageNameLen = 2; 502 DCHECK(!input_method.language_codes().empty()); 503 const std::string language_code = input_method.language_codes().at(0); 504 text = StringToUpperASCII(base::UTF8ToUTF16(language_code)).substr( 505 0, kMaxLanguageNameLen); 506 } 507 DCHECK(!text.empty()) << input_method.id(); 508 return text; 509} 510 511base::string16 InputMethodUtil::GetInputMethodMediumName( 512 const InputMethodDescriptor& input_method) const { 513 // For the "Your input method has changed to..." bubble. In most cases 514 // it uses the same name as the short name, unless found in a table 515 // for medium length names. 516 for (size_t i = 0; i < kMappingImeIdToMediumLenNameResourceIdLen; ++i) { 517 if (kMappingImeIdToMediumLenNameResourceId[i].input_method_id == 518 input_method.id()) { 519 return delegate_->GetLocalizedString( 520 kMappingImeIdToMediumLenNameResourceId[i].resource_id); 521 } 522 } 523 return GetInputMethodShortName(input_method); 524} 525 526base::string16 InputMethodUtil::GetInputMethodLongName( 527 const InputMethodDescriptor& input_method) const { 528 if (!input_method.name().empty()) { 529 // If the descriptor has a name, use it. 530 return base::UTF8ToUTF16(input_method.name()); 531 } 532 533 // We don't show language here. Name of keyboard layout or input method 534 // usually imply (or explicitly include) its language. 535 536 // Special case for German, French and Dutch: these languages have multiple 537 // keyboard layouts and share the same layout of keyboard (Belgian). We need 538 // to show explicitly the language for the layout. For Arabic, Amharic, and 539 // Indic languages: they share "Standard Input Method". 540 const base::string16 standard_input_method_text = 541 delegate_->GetLocalizedString( 542 IDS_OPTIONS_SETTINGS_LANGUAGES_M17N_STANDARD_INPUT_METHOD); 543 DCHECK(!input_method.language_codes().empty()); 544 const std::string language_code = input_method.language_codes().at(0); 545 546 // Before translate the string, convert the input method id to legacy xkb id 547 // if possible. 548 // TODO(shuchen): the GetInputMethodLongName() method should be removed when 549 // finish the wrapping of xkb to extension. 550 base::string16 text = TranslateString( 551 extension_ime_util::MaybeGetLegacyXkbId(input_method.id())); 552 if (text == standard_input_method_text || 553 language_code == "de" || 554 language_code == "fr" || 555 language_code == "nl") { 556 const base::string16 language_name = delegate_->GetDisplayLanguageName( 557 language_code); 558 559 text = language_name + base::UTF8ToUTF16(" - ") + text; 560 } 561 562 DCHECK(!text.empty()); 563 return text; 564} 565 566const InputMethodDescriptor* InputMethodUtil::GetInputMethodDescriptorFromId( 567 const std::string& input_method_id) const { 568 InputMethodIdToDescriptorMap::const_iterator iter = 569 id_to_descriptor_.find(input_method_id); 570 if (iter == id_to_descriptor_.end()) 571 return NULL; 572 return &(iter->second); 573} 574 575bool InputMethodUtil::GetInputMethodIdsFromLanguageCode( 576 const std::string& normalized_language_code, 577 InputMethodType type, 578 std::vector<std::string>* out_input_method_ids) const { 579 return GetInputMethodIdsFromLanguageCodeInternal( 580 language_code_to_ids_, 581 normalized_language_code, type, out_input_method_ids); 582} 583 584bool InputMethodUtil::GetInputMethodIdsFromLanguageCodeInternal( 585 const std::multimap<std::string, std::string>& language_code_to_ids, 586 const std::string& normalized_language_code, 587 InputMethodType type, 588 std::vector<std::string>* out_input_method_ids) const { 589 DCHECK(out_input_method_ids); 590 out_input_method_ids->clear(); 591 592 bool result = false; 593 std::pair<LanguageCodeToIdsMap::const_iterator, 594 LanguageCodeToIdsMap::const_iterator> range = 595 language_code_to_ids.equal_range(normalized_language_code); 596 for (LanguageCodeToIdsMap::const_iterator iter = range.first; 597 iter != range.second; ++iter) { 598 const std::string& input_method_id = iter->second; 599 if ((type == kAllInputMethods) || IsKeyboardLayout(input_method_id)) { 600 out_input_method_ids->push_back(input_method_id); 601 result = true; 602 } 603 } 604 if ((type == kAllInputMethods) && !result) { 605 DVLOG(1) << "Unknown language code: " << normalized_language_code; 606 } 607 return result; 608} 609 610void InputMethodUtil::GetFirstLoginInputMethodIds( 611 const std::string& language_code, 612 const InputMethodDescriptor& current_input_method, 613 std::vector<std::string>* out_input_method_ids) const { 614 out_input_method_ids->clear(); 615 616 // First, add the current keyboard layout (one used on the login screen). 617 out_input_method_ids->push_back(current_input_method.id()); 618 619 const std::string current_layout 620 = current_input_method.GetPreferredKeyboardLayout(); 621 for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kDefaultInputMethodRecommendation); 622 ++i) { 623 if (kDefaultInputMethodRecommendation[i].locale == language_code && 624 kDefaultInputMethodRecommendation[i].layout == current_layout) { 625 out_input_method_ids->push_back( 626 kDefaultInputMethodRecommendation[i].input_method_id); 627 return; 628 } 629 } 630 631 // Second, find the most popular input method associated with the 632 // current UI language. The input method IDs returned from 633 // GetInputMethodIdsFromLanguageCode() are sorted by popularity, hence 634 // our basic strategy is to pick the first one, but it's a bit more 635 // complicated as shown below. 636 std::string most_popular_id; 637 std::vector<std::string> input_method_ids; 638 // This returns the input methods sorted by popularity. 639 GetInputMethodIdsFromLanguageCode( 640 language_code, kAllInputMethods, &input_method_ids); 641 for (size_t i = 0; i < input_method_ids.size(); ++i) { 642 const std::string& input_method_id = input_method_ids[i]; 643 // Pick the first one. 644 if (most_popular_id.empty()) 645 most_popular_id = input_method_id; 646 647 // Check if there is one that matches the current keyboard layout, but 648 // not the current keyboard itself. This is useful if there are 649 // multiple keyboard layout choices for one input method. For 650 // instance, Mozc provides three choices: mozc (US keyboard), mozc-jp 651 // (JP keyboard), mozc-dv (Dvorak). 652 const InputMethodDescriptor* descriptor = 653 GetInputMethodDescriptorFromId(input_method_id); 654 if (descriptor && 655 descriptor->id() != current_input_method.id() && 656 descriptor->GetPreferredKeyboardLayout() == 657 current_input_method.GetPreferredKeyboardLayout()) { 658 most_popular_id = input_method_id; 659 break; 660 } 661 } 662 // Add the most popular input method ID, if it's different from the 663 // current input method. 664 if (most_popular_id != current_input_method.id() && 665 // TODO(yusukes): Remove this hack when we remove the "english-m" IME. 666 most_popular_id != "english-m") { 667 out_input_method_ids->push_back(most_popular_id); 668 } 669} 670 671void InputMethodUtil::GetLanguageCodesFromInputMethodIds( 672 const std::vector<std::string>& input_method_ids, 673 std::vector<std::string>* out_language_codes) const { 674 out_language_codes->clear(); 675 676 for (size_t i = 0; i < input_method_ids.size(); ++i) { 677 const std::string& input_method_id = input_method_ids[i]; 678 const InputMethodDescriptor* input_method = 679 GetInputMethodDescriptorFromId(input_method_id); 680 if (!input_method) { 681 DVLOG(1) << "Unknown input method ID: " << input_method_ids[i]; 682 continue; 683 } 684 DCHECK(!input_method->language_codes().empty()); 685 const std::string language_code = input_method->language_codes().at(0); 686 // Add it if it's not already present. 687 if (std::count(out_language_codes->begin(), out_language_codes->end(), 688 language_code) == 0) { 689 out_language_codes->push_back(language_code); 690 } 691 } 692} 693 694std::string InputMethodUtil::GetLanguageDefaultInputMethodId( 695 const std::string& language_code) { 696 std::vector<std::string> candidates; 697 GetInputMethodIdsFromLanguageCode( 698 language_code, input_method::kKeyboardLayoutsOnly, &candidates); 699 if (candidates.size()) 700 return candidates.front(); 701 702 return std::string(); 703} 704 705bool InputMethodUtil::MigrateXkbInputMethods( 706 std::vector<std::string>* input_method_ids) { 707 bool rewritten = false; 708 std::vector<std::string>& ids = *input_method_ids; 709 for (size_t i = 0; i < ids.size(); ++i) { 710 std::string id = 711 extension_ime_util::GetInputMethodIDByKeyboardLayout(ids[i]); 712 // Migrates the old chinese ime id to new ones. 713 // TODO(shuchen): Change the function name to MigrateInputMethods, 714 // and create an abstract layer to map a comprehensive input method id to 715 // the real extension based input method id. 716 // e.g. "zh-t-i0-pinyin" maps to 717 // "_comp_id_gjaehgfemfahhmlgpdfknkhdnemmolopzh-t-i0-pinyin". 718 // See crbug.com/358083. 719 for (size_t j = 0; j < arraysize(kOldChineseExtensionIds); ++j) { 720 size_t pos = id.find(kOldChineseExtensionIds[j]); 721 if (pos != std::string::npos) { 722 id.replace(pos, kExtensionIdLen, kNewChineseExtensionId); 723 break; 724 } 725 } 726 if (id != ids[i]) { 727 ids[i] = id; 728 rewritten = true; 729 } 730 } 731 if (rewritten) { 732 // Removes the duplicates. 733 std::vector<std::string> new_ids; 734 for (size_t i = 0; i < ids.size(); ++i) { 735 if (std::find(new_ids.begin(), new_ids.end(), ids[i]) == new_ids.end()) 736 new_ids.push_back(ids[i]); 737 } 738 ids.swap(new_ids); 739 } 740 return rewritten; 741} 742 743void InputMethodUtil::UpdateHardwareLayoutCache() { 744 DCHECK(thread_checker_.CalledOnValidThread()); 745 hardware_layouts_.clear(); 746 hardware_login_layouts_.clear(); 747 Tokenize(delegate_->GetHardwareKeyboardLayouts(), ",", &hardware_layouts_); 748 MigrateXkbInputMethods(&hardware_layouts_); 749 750 for (size_t i = 0; i < hardware_layouts_.size(); ++i) { 751 if (IsLoginKeyboard(hardware_layouts_[i])) 752 hardware_login_layouts_.push_back(hardware_layouts_[i]); 753 } 754 if (hardware_layouts_.empty()) { 755 // This is totally fine if it's empty. The hardware keyboard layout is 756 // not stored if startup_manifest.json (OEM customization data) is not 757 // present (ex. Cr48 doen't have that file). 758 hardware_layouts_.push_back(GetFallbackInputMethodDescriptor().id()); 759 } 760 761 if (hardware_login_layouts_.empty()) 762 hardware_login_layouts_.push_back(GetFallbackInputMethodDescriptor().id()); 763} 764 765void InputMethodUtil::SetHardwareKeyboardLayoutForTesting( 766 const std::string& layout) { 767 delegate_->SetHardwareKeyboardLayoutForTesting(layout); 768 UpdateHardwareLayoutCache(); 769} 770 771const std::vector<std::string>& 772 InputMethodUtil::GetHardwareInputMethodIds() { 773 DCHECK(thread_checker_.CalledOnValidThread()); 774 // Once the initialization is done, at least one input method should be set. 775 if (hardware_layouts_.empty()) 776 UpdateHardwareLayoutCache(); 777 return hardware_layouts_; 778} 779 780const std::vector<std::string>& 781 InputMethodUtil::GetHardwareLoginInputMethodIds() { 782 DCHECK(thread_checker_.CalledOnValidThread()); 783 // Once the initialization is done, at least one input method should be set. 784 if (hardware_login_layouts_.empty()) 785 UpdateHardwareLayoutCache(); 786 return hardware_login_layouts_; 787} 788 789bool InputMethodUtil::IsLoginKeyboard(const std::string& input_method_id) 790 const { 791 const InputMethodDescriptor* ime = 792 GetInputMethodDescriptorFromId(input_method_id); 793 return ime ? ime->is_login_keyboard() : false; 794} 795 796void InputMethodUtil::SetComponentExtensions( 797 const InputMethodDescriptors& imes) { 798 for (size_t i = 0; i < imes.size(); ++i) { 799 const InputMethodDescriptor& input_method = imes[i]; 800 DCHECK(!input_method.language_codes().empty()); 801 const std::vector<std::string>& language_codes = 802 input_method.language_codes(); 803 id_to_language_code_[input_method.id()] = language_codes[0]; 804 id_to_descriptor_[input_method.id()] = input_method; 805 806 typedef LanguageCodeToIdsMap::const_iterator It; 807 for (size_t j = 0; j < language_codes.size(); ++j) { 808 std::pair<It, It> range = 809 language_code_to_ids_.equal_range(language_codes[j]); 810 It it = range.first; 811 for (; it != range.second; ++it) { 812 if (it->second == input_method.id()) 813 break; 814 } 815 if (it == range.second) 816 language_code_to_ids_.insert( 817 std::make_pair(language_codes[j], input_method.id())); 818 } 819 } 820} 821 822void InputMethodUtil::InitXkbInputMethodsForTesting() { 823 if (!extension_ime_util::UseWrappedExtensionKeyboardLayouts()) 824 return; 825 scoped_ptr<InputMethodDescriptors> original_imes = 826 InputMethodWhitelist().GetSupportedInputMethods(); 827 InputMethodDescriptors whitelist_imes; 828 for (size_t i = 0; i < original_imes->size(); ++i) { 829 const InputMethodDescriptor& ime = (*original_imes)[i]; 830 whitelist_imes.push_back(InputMethodDescriptor( 831 extension_ime_util::GetInputMethodIDByKeyboardLayout(ime.id()), 832 "", 833 ime.indicator(), 834 ime.keyboard_layouts(), 835 ime.language_codes(), 836 ime.is_login_keyboard(), 837 ime.options_page_url(), 838 ime.input_view_url())); 839 } 840 SetComponentExtensions(whitelist_imes); 841} 842 843InputMethodDescriptor InputMethodUtil::GetFallbackInputMethodDescriptor() { 844 std::vector<std::string> layouts; 845 layouts.push_back("us"); 846 std::vector<std::string> languages; 847 languages.push_back("en-US"); 848 return InputMethodDescriptor( 849 extension_ime_util::GetInputMethodIDByKeyboardLayout("xkb:us::eng"), 850 "", 851 "US", 852 layouts, 853 languages, 854 true, // login keyboard. 855 GURL(), // options page, not available. 856 GURL()); // input view page, not available. 857} 858 859void InputMethodUtil::ReloadInternalMaps() { 860 if (supported_input_methods_->size() <= 1) { 861 DVLOG(1) << "GetSupportedInputMethods returned a fallback ID"; 862 // TODO(yusukes): Handle this error in nicer way. 863 } 864 865 // Clear the existing maps. 866 language_code_to_ids_.clear(); 867 id_to_language_code_.clear(); 868 id_to_descriptor_.clear(); 869 xkb_id_to_descriptor_.clear(); 870 871 for (size_t i = 0; i < supported_input_methods_->size(); ++i) { 872 const InputMethodDescriptor& input_method = 873 supported_input_methods_->at(i); 874 const std::vector<std::string>& language_codes = 875 input_method.language_codes(); 876 for (size_t i = 0; i < language_codes.size(); ++i) { 877 language_code_to_ids_.insert( 878 std::make_pair(language_codes[i], input_method.id())); 879 // Remember the pairs. 880 id_to_language_code_.insert( 881 std::make_pair(input_method.id(), language_codes[i])); 882 } 883 id_to_descriptor_.insert( 884 std::make_pair(input_method.id(), input_method)); 885 if (IsKeyboardLayout(input_method.id())) { 886 xkb_id_to_descriptor_.insert( 887 std::make_pair(input_method.GetPreferredKeyboardLayout(), 888 input_method)); 889 } 890 } 891} 892 893} // namespace input_method 894} // namespace chromeos 895