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