input_method_library.cc revision 731df977c0511bca2206b5f333555b1205ff1f43
1// Copyright (c) 2010 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/cros/input_method_library.h"
6
7#include <glib.h>
8#include <signal.h>
9
10#include "unicode/uloc.h"
11
12#include "base/basictypes.h"
13#include "base/message_loop.h"
14#include "base/string_util.h"
15#include "chrome/browser/browser_process.h"
16#include "chrome/browser/browser_thread.h"
17#include "chrome/browser/chromeos/cros/cros_library.h"
18#include "chrome/browser/chromeos/cros/keyboard_library.h"
19#include "chrome/browser/chromeos/input_method/input_method_util.h"
20#include "chrome/browser/chromeos/language_preferences.h"
21#include "chrome/common/notification_observer.h"
22#include "chrome/common/notification_registrar.h"
23#include "chrome/common/notification_service.h"
24
25namespace {
26const char kIBusDaemonPath[] = "/usr/bin/ibus-daemon";
27const char kCandidateWindowPath[] = "/opt/google/chrome/candidate_window";
28
29// Finds a property which has |new_prop.key| from |prop_list|, and replaces the
30// property with |new_prop|. Returns true if such a property is found.
31bool FindAndUpdateProperty(const chromeos::ImeProperty& new_prop,
32                           chromeos::ImePropertyList* prop_list) {
33  for (size_t i = 0; i < prop_list->size(); ++i) {
34    chromeos::ImeProperty& prop = prop_list->at(i);
35    if (prop.key == new_prop.key) {
36      const int saved_id = prop.selection_item_id;
37      // Update the list except the radio id. As written in
38      // chromeos_input_method.h, |prop.selection_item_id| is dummy.
39      prop = new_prop;
40      prop.selection_item_id = saved_id;
41      return true;
42    }
43  }
44  return false;
45}
46
47}  // namespace
48
49namespace chromeos {
50
51class InputMethodLibraryImpl : public InputMethodLibrary,
52                               public NotificationObserver {
53 public:
54  InputMethodLibraryImpl()
55      : input_method_status_connection_(NULL),
56        previous_input_method_("", "", "", ""),
57        current_input_method_("", "", "", ""),
58        should_launch_ime_(false),
59        ime_connected_(false),
60        defer_ime_startup_(false),
61        should_change_input_method_(false),
62        ibus_daemon_process_id_(0),
63        candidate_window_process_id_(0),
64        failure_count_(0) {
65    scoped_ptr<InputMethodDescriptors> input_method_descriptors(
66        CreateFallbackInputMethodDescriptors());
67    current_input_method_ = input_method_descriptors->at(0);
68    if (CrosLibrary::Get()->EnsureLoaded()) {
69      current_input_method_id_ = chromeos::GetHardwareKeyboardLayoutName();
70    }
71    // Observe APP_EXITING to stop input method processes gracefully.
72    // Note that even if we fail to stop input method processes from
73    // Chrome in case of a sudden crash, we have a way to do it from an
74    // upstart script. See crosbug.com/6515 and crosbug.com/6995 for
75    // details.
76    notification_registrar_.Add(this, NotificationType::APP_EXITING,
77                                NotificationService::AllSources());
78  }
79
80  ~InputMethodLibraryImpl() {
81  }
82
83  void AddObserver(Observer* observer) {
84    observers_.AddObserver(observer);
85  }
86
87  void RemoveObserver(Observer* observer) {
88    observers_.RemoveObserver(observer);
89  }
90
91  InputMethodDescriptors* GetActiveInputMethods() {
92    chromeos::InputMethodDescriptors* result = NULL;
93    // The connection does not need to be alive, but it does need to be created.
94    if (EnsureLoadedAndStarted()) {
95      result = chromeos::GetActiveInputMethods(input_method_status_connection_);
96    }
97    if (!result || result->empty()) {
98      result = CreateFallbackInputMethodDescriptors();
99    }
100    return result;
101  }
102
103  size_t GetNumActiveInputMethods() {
104    scoped_ptr<InputMethodDescriptors> input_methods(GetActiveInputMethods());
105    return input_methods->size();
106  }
107
108  InputMethodDescriptors* GetSupportedInputMethods() {
109    InputMethodDescriptors* result = NULL;
110    // The connection does not need to be alive, but it does need to be created.
111    if (EnsureLoadedAndStarted()) {
112      result = chromeos::GetSupportedInputMethods(
113          input_method_status_connection_);
114    }
115    if (!result || result->empty()) {
116      result = CreateFallbackInputMethodDescriptors();
117    }
118    return result;
119  }
120
121  void ChangeInputMethod(const std::string& input_method_id) {
122    current_input_method_id_ = input_method_id;
123    if (EnsureLoadedAndStarted()) {
124      if (input_method_id != chromeos::GetHardwareKeyboardLayoutName()) {
125        StartInputMethodProcesses();
126      }
127      chromeos::ChangeInputMethod(
128          input_method_status_connection_, input_method_id.c_str());
129    }
130  }
131
132  void SetImePropertyActivated(const std::string& key, bool activated) {
133    DCHECK(!key.empty());
134    if (EnsureLoadedAndStarted()) {
135      chromeos::SetImePropertyActivated(
136          input_method_status_connection_, key.c_str(), activated);
137    }
138  }
139
140  bool InputMethodIsActivated(const std::string& input_method_id) {
141    scoped_ptr<InputMethodDescriptors> active_input_method_descriptors(
142        CrosLibrary::Get()->GetInputMethodLibrary()->GetActiveInputMethods());
143    for (size_t i = 0; i < active_input_method_descriptors->size(); ++i) {
144      if (active_input_method_descriptors->at(i).id == input_method_id) {
145        return true;
146      }
147    }
148    return false;
149  }
150
151  bool GetImeConfig(const char* section, const char* config_name,
152                    ImeConfigValue* out_value) {
153    bool success = false;
154    if (EnsureLoadedAndStarted()) {
155      success = chromeos::GetImeConfig(input_method_status_connection_,
156                                       section, config_name, out_value);
157    }
158    return success;
159  }
160
161  bool SetImeConfig(const char* section, const char* config_name,
162                    const ImeConfigValue& value) {
163    MaybeStartOrStopInputMethodProcesses(section, config_name, value);
164
165    const ConfigKeyType key = std::make_pair(section, config_name);
166    current_config_values_[key] = value;
167    if (ime_connected_) {
168      pending_config_requests_[key] = value;
169      FlushImeConfig();
170    }
171    return pending_config_requests_.empty();
172  }
173
174  virtual const InputMethodDescriptor& previous_input_method() const {
175    return previous_input_method_;
176  }
177  virtual const InputMethodDescriptor& current_input_method() const {
178    return current_input_method_;
179  }
180
181  virtual const ImePropertyList& current_ime_properties() const {
182    return current_ime_properties_;
183  }
184
185 private:
186  // Starts or stops the input method processes based on the current state.
187  void MaybeStartOrStopInputMethodProcesses(
188      const char* section,
189      const char* config_name,
190      const ImeConfigValue& value) {
191    if (!strcmp(language_prefs::kGeneralSectionName, section) &&
192        !strcmp(language_prefs::kPreloadEnginesConfigName, config_name)) {
193      if (EnsureLoadedAndStarted()) {
194        // If there are no input methods other than one for the hardware
195        // keyboard, we'll stop the input method processes.
196        if (value.type == ImeConfigValue::kValueTypeStringList &&
197            value.string_list_value.size() == 1 &&
198            value.string_list_value[0] ==
199            chromeos::GetHardwareKeyboardLayoutName()) {
200          StopInputMethodProcesses();
201        } else if (!defer_ime_startup_) {
202          StartInputMethodProcesses();
203        }
204        chromeos::SetActiveInputMethods(input_method_status_connection_, value);
205      }
206    }
207  }
208
209  // Flushes the input method config data. The config data is queued up in
210  // |pending_config_requests_| until the config backend (ibus-memconf)
211  // starts. Since there is no good way to get notified when the config
212  // backend starts, we use a timer to periodically attempt to send the
213  // config data to the config backend.
214  void FlushImeConfig() {
215    bool active_input_methods_are_changed = false;
216    bool completed = false;
217    if (EnsureLoadedAndStarted()) {
218      InputMethodConfigRequests::iterator iter =
219          pending_config_requests_.begin();
220      while (iter != pending_config_requests_.end()) {
221        const std::string& section = iter->first.first;
222        const std::string& config_name = iter->first.second;
223        const ImeConfigValue& value = iter->second;
224        if (chromeos::SetImeConfig(input_method_status_connection_,
225                                   section.c_str(),
226                                   config_name.c_str(),
227                                   value)) {
228          // Check if it's a change in active input methods.
229          if (config_name == language_prefs::kPreloadEnginesConfigName) {
230            active_input_methods_are_changed = true;
231          }
232          // Successfully sent. Remove the command and proceed to the next one.
233          pending_config_requests_.erase(iter++);
234        } else {
235          // If SetImeConfig() fails, subsequent calls will likely fail.
236          break;
237        }
238      }
239      if (pending_config_requests_.empty()) {
240        // Calls to ChangeInputMethod() will fail if the input method has not
241        // yet been added to preload_engines.  As such, the call is deferred
242        // until after all config values have been sent to the IME process.
243        if (should_change_input_method_) {
244          if (chromeos::ChangeInputMethod(input_method_status_connection_,
245                                          current_input_method_id_.c_str())) {
246            should_change_input_method_ = false;
247            completed = true;
248            active_input_methods_are_changed = true;
249          }
250        } else {
251          completed = true;
252        }
253      }
254    }
255
256    if (completed) {
257      timer_.Stop();  // no-op if it's not running.
258    } else {
259      // Flush is not completed. Start a timer if it's not yet running.
260      if (!timer_.IsRunning()) {
261        static const int64 kTimerIntervalInMsec = 100;
262        failure_count_ = 0;
263        timer_.Start(base::TimeDelta::FromMilliseconds(kTimerIntervalInMsec),
264                     this, &InputMethodLibraryImpl::FlushImeConfig);
265      } else {
266        // The timer is already running. We'll give up if it reaches the
267        // max retry count.
268        static const int kMaxRetries = 15;
269        ++failure_count_;
270        if (failure_count_ > kMaxRetries) {
271          LOG(ERROR) << "FlushImeConfig: Max retries exceeded. "
272                     << "current_input_method_id: " << current_input_method_id_
273                     << " pending_config_requests.size: "
274                     << pending_config_requests_.size();
275          timer_.Stop();
276        }
277      }
278    }
279    if (active_input_methods_are_changed) {
280      FOR_EACH_OBSERVER(Observer, observers_, ActiveInputMethodsChanged(this));
281    }
282  }
283
284  static void InputMethodChangedHandler(
285      void* object,
286      const chromeos::InputMethodDescriptor& current_input_method) {
287    // The handler is called when the input method method change is
288    // notified via a DBus connection. Since the DBus notificatiosn are
289    // handled in the UI thread, we can assume that this functionalways
290    // runs on the UI thread, but just in case.
291    if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) {
292      LOG(ERROR) << "Not on UI thread";
293      return;
294    }
295
296    InputMethodLibraryImpl* input_method_library =
297        static_cast<InputMethodLibraryImpl*>(object);
298    input_method_library->ChangeCurrentInputMethod(current_input_method);
299  }
300
301  static void RegisterPropertiesHandler(
302      void* object, const ImePropertyList& prop_list) {
303    // See comments in InputMethodChangedHandler.
304    if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) {
305      LOG(ERROR) << "Not on UI thread";
306      return;
307    }
308
309    InputMethodLibraryImpl* input_method_library =
310        static_cast<InputMethodLibraryImpl*>(object);
311    input_method_library->RegisterProperties(prop_list);
312  }
313
314  static void UpdatePropertyHandler(
315      void* object, const ImePropertyList& prop_list) {
316    // See comments in InputMethodChangedHandler.
317    if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) {
318      LOG(ERROR) << "Not on UI thread";
319      return;
320    }
321
322    InputMethodLibraryImpl* input_method_library =
323        static_cast<InputMethodLibraryImpl*>(object);
324    input_method_library->UpdateProperty(prop_list);
325  }
326
327  static void ConnectionChangeHandler(void* object, bool connected) {
328    // See comments in InputMethodChangedHandler.
329    if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) {
330      LOG(ERROR) << "Not on UI thread";
331      return;
332    }
333
334    InputMethodLibraryImpl* input_method_library =
335        static_cast<InputMethodLibraryImpl*>(object);
336    input_method_library->ime_connected_ = connected;
337    if (connected) {
338      input_method_library->pending_config_requests_.clear();
339      input_method_library->pending_config_requests_.insert(
340          input_method_library->current_config_values_.begin(),
341          input_method_library->current_config_values_.end());
342      input_method_library->should_change_input_method_ = true;
343      input_method_library->FlushImeConfig();
344    } else {
345      // Stop attempting to resend config data, since it will continue to fail.
346      input_method_library->timer_.Stop();  // no-op if it's not running.
347    }
348  }
349
350  bool EnsureStarted() {
351    if (!input_method_status_connection_) {
352      input_method_status_connection_ = chromeos::MonitorInputMethodStatus(
353          this,
354          &InputMethodChangedHandler,
355          &RegisterPropertiesHandler,
356          &UpdatePropertyHandler,
357          &ConnectionChangeHandler);
358    }
359    return true;
360  }
361
362  bool EnsureLoadedAndStarted() {
363    return CrosLibrary::Get()->EnsureLoaded() &&
364           EnsureStarted();
365  }
366
367  void ChangeCurrentInputMethod(const InputMethodDescriptor& new_input_method) {
368    // Change the keyboard layout to a preferred layout for the input method.
369    CrosLibrary::Get()->GetKeyboardLibrary()->SetCurrentKeyboardLayoutByName(
370        new_input_method.keyboard_layout);
371
372    if (current_input_method_.id != new_input_method.id) {
373      previous_input_method_ = current_input_method_;
374      current_input_method_ = new_input_method;
375    }
376    FOR_EACH_OBSERVER(Observer, observers_, InputMethodChanged(this));
377  }
378
379  void RegisterProperties(const ImePropertyList& prop_list) {
380    // |prop_list| might be empty. This means "clear all properties."
381    current_ime_properties_ = prop_list;
382    FOR_EACH_OBSERVER(Observer, observers_, ImePropertiesChanged(this));
383  }
384
385  void StartInputMethodProcesses() {
386    should_launch_ime_ = true;
387    MaybeLaunchInputMethodProcesses();
388  }
389
390  void UpdateProperty(const ImePropertyList& prop_list) {
391    for (size_t i = 0; i < prop_list.size(); ++i) {
392      FindAndUpdateProperty(prop_list[i], &current_ime_properties_);
393    }
394    FOR_EACH_OBSERVER(Observer, observers_, ImePropertiesChanged(this));
395  }
396
397  // Launches an input method procsess specified by the given command
398  // line. On success, returns true and stores the process ID in
399  // |process_id|. Otherwise, returns false, and the contents of
400  // |process_id| is untouched. OnImeShutdown will be called when the
401  // process terminates.
402  bool LaunchInputMethodProcess(const std::string& command_line,
403                                int* process_id) {
404    GError *error = NULL;
405    gchar **argv = NULL;
406    gint argc = NULL;
407    // TODO(zork): export "LD_PRELOAD=/usr/lib/libcrash.so"
408    if (!g_shell_parse_argv(command_line.c_str(), &argc, &argv, &error)) {
409      LOG(ERROR) << "Could not parse command: " << error->message;
410      g_error_free(error);
411      return false;
412    }
413
414    int pid = 0;
415    const GSpawnFlags kFlags = G_SPAWN_DO_NOT_REAP_CHILD;
416    const gboolean result = g_spawn_async(NULL, argv, NULL,
417                                          kFlags, NULL, NULL,
418                                          &pid, &error);
419    g_strfreev(argv);
420    if (!result) {
421      LOG(ERROR) << "Could not launch: " << command_line << ": "
422                 << error->message;
423      g_error_free(error);
424      return false;
425    }
426    g_child_watch_add(pid, reinterpret_cast<GChildWatchFunc>(OnImeShutdown),
427                      this);
428
429    *process_id = pid;
430    return  true;
431  }
432
433  // Launches input method processes if these are not yet running.
434  void MaybeLaunchInputMethodProcesses() {
435    if (!should_launch_ime_) {
436      return;
437    }
438
439    if (ibus_daemon_process_id_ == 0) {
440      // TODO(zork): Send output to /var/log/ibus.log
441      const std::string ibus_daemon_command_line =
442          StringPrintf("%s --panel=disable --cache=none --restart --replace",
443                       kIBusDaemonPath);
444      if (!LaunchInputMethodProcess(ibus_daemon_command_line,
445                                    &ibus_daemon_process_id_)) {
446        // On failure, we should not attempt to launch candidate_window.
447        return;
448      }
449    }
450
451    if (candidate_window_process_id_ == 0) {
452      // Pass the UI language info to candidate_window via --lang flag.
453      const std::string candidate_window_command_line =
454          StringPrintf("%s --lang=%s", kCandidateWindowPath,
455                       g_browser_process->GetApplicationLocale().c_str());
456      if (!LaunchInputMethodProcess(candidate_window_command_line,
457                                    &candidate_window_process_id_)) {
458        // Return here just in case we add more code below.
459        return;
460      }
461    }
462  }
463
464  static void OnImeShutdown(int pid,
465                            int status,
466                            InputMethodLibraryImpl* library) {
467    g_spawn_close_pid(pid);
468    if (library->ibus_daemon_process_id_ == pid) {
469      library->ibus_daemon_process_id_ = 0;
470    } else if (library->candidate_window_process_id_ == pid) {
471      library->candidate_window_process_id_ = 0;
472    }
473
474    // Restart input method processes if needed.
475    library->MaybeLaunchInputMethodProcesses();
476  }
477
478  void StopInputMethodProcesses() {
479    should_launch_ime_ = false;
480    if (ibus_daemon_process_id_) {
481      const std::string xkb_engine_name =
482          chromeos::GetHardwareKeyboardLayoutName();
483      // We should not use chromeos::ChangeInputMethod() here since without the
484      // ibus-daemon process, ChangeCurrentInputMethod() callback function which
485      // actually changes the XKB layout will not be called.
486      CrosLibrary::Get()->GetKeyboardLibrary()->SetCurrentKeyboardLayoutByName(
487          chromeos::input_method::GetKeyboardLayoutName(xkb_engine_name));
488      kill(ibus_daemon_process_id_, SIGTERM);
489      ibus_daemon_process_id_ = 0;
490    }
491    if (candidate_window_process_id_) {
492      kill(candidate_window_process_id_, SIGTERM);
493      candidate_window_process_id_ = 0;
494    }
495  }
496
497  void SetDeferImeStartup(bool defer) {
498    LOG(INFO) << "Setting DeferImeStartup to " << defer;
499    defer_ime_startup_ = defer;
500  }
501
502  // NotificationObserver implementation:
503  void Observe(NotificationType type,
504               const NotificationSource& source,
505               const NotificationDetails& details) {
506    // Stop the input processes on browser shutdown.
507    if (type.value == NotificationType::APP_EXITING) {
508      StopInputMethodProcesses();
509    }
510  }
511
512  // A reference to the language api, to allow callbacks when the input method
513  // status changes.
514  InputMethodStatusConnection* input_method_status_connection_;
515  ObserverList<Observer> observers_;
516
517  // The input method which was/is selected.
518  InputMethodDescriptor previous_input_method_;
519  InputMethodDescriptor current_input_method_;
520
521  // The input method properties which the current input method uses. The list
522  // might be empty when no input method is used.
523  ImePropertyList current_ime_properties_;
524
525  typedef std::pair<std::string, std::string> ConfigKeyType;
526  typedef std::map<ConfigKeyType, ImeConfigValue> InputMethodConfigRequests;
527  // SetImeConfig requests that are not yet completed.
528  // Use a map to queue config requests, so we only send the last request for
529  // the same config key (i.e. we'll discard ealier requests for the same
530  // config key). As we discard old requests for the same config key, the order
531  // of requests doesn't matter, so it's safe to use a map.
532  InputMethodConfigRequests pending_config_requests_;
533
534  // Values that have been set via SetImeConfig().  We keep a copy available to
535  // resend if the ime restarts and loses its state.
536  InputMethodConfigRequests current_config_values_;
537
538  // A timer for retrying to send |pendning_config_commands_| to the input
539  // method config daemon.
540  base::OneShotTimer<InputMethodLibraryImpl> timer_;
541
542  // This is used to register this object to APP_EXITING notification.
543  NotificationRegistrar notification_registrar_;
544
545  // True if we should launch the input method processes.
546  bool should_launch_ime_;
547  // True if the connection to the IBus daemon is alive.
548  bool ime_connected_;
549  // If true, we'll defer the startup until a non-default method is
550  // activated.
551  bool defer_ime_startup_;
552  // The ID of the current input method (ex. "mozc").
553  std::string current_input_method_id_;
554  // True if we should change the input method once the queue of the
555  // pending config requests becomes empty.
556  bool should_change_input_method_;
557
558  // The process id of the IBus daemon. 0 if it's not running. The process
559  // ID 0 is not used in Linux, hence it's safe to use 0 for this purpose.
560  int ibus_daemon_process_id_;
561  // The process id of the candidate window. 0 if it's not running.
562  int candidate_window_process_id_;
563  // The failure count of config flush attempts.
564  int failure_count_;
565
566  DISALLOW_COPY_AND_ASSIGN(InputMethodLibraryImpl);
567};
568
569InputMethodLibraryImpl::Observer::~Observer() {}
570
571class InputMethodLibraryStubImpl : public InputMethodLibrary {
572 public:
573  InputMethodLibraryStubImpl()
574      : previous_input_method_("", "", "", ""),
575        current_input_method_("", "", "", "") {
576  }
577
578  ~InputMethodLibraryStubImpl() {}
579  void AddObserver(Observer* observer) {}
580  void RemoveObserver(Observer* observer) {}
581
582  InputMethodDescriptors* GetActiveInputMethods() {
583    return CreateRealisticInputMethodDescriptors();
584  }
585
586
587  size_t GetNumActiveInputMethods() {
588    scoped_ptr<InputMethodDescriptors> descriptors(
589        CreateRealisticInputMethodDescriptors());
590    return descriptors->size();
591  }
592
593  InputMethodDescriptors* GetSupportedInputMethods() {
594    return CreateRealisticInputMethodDescriptors();
595  }
596
597  void ChangeInputMethod(const std::string& input_method_id) {}
598  void SetImePropertyActivated(const std::string& key, bool activated) {}
599
600  bool InputMethodIsActivated(const std::string& input_method_id) {
601    return true;
602  }
603
604  bool GetImeConfig(const char* section,
605                    const char* config_name,
606                    ImeConfigValue* out_value) {
607    return false;
608  }
609
610  bool SetImeConfig(const char* section,
611                    const char* config_name,
612                    const ImeConfigValue& value) {
613    return false;
614  }
615
616  virtual const InputMethodDescriptor& previous_input_method() const {
617    return previous_input_method_;
618  }
619
620  virtual const InputMethodDescriptor& current_input_method() const {
621    return current_input_method_;
622  }
623
624  virtual const ImePropertyList& current_ime_properties() const {
625    return current_ime_properties_;
626  }
627
628  virtual void StartInputMethodProcesses() {}
629  virtual void StopInputMethodProcesses() {}
630  virtual void SetDeferImeStartup(bool defer) {}
631
632 private:
633  // Creates realistic input method descriptors that can be used for
634  // testing Chrome OS version of chrome on regular Linux desktops.
635  InputMethodDescriptors* CreateRealisticInputMethodDescriptors() {
636    InputMethodDescriptors* descriptions = new InputMethodDescriptors;
637    // The list is created from output of gen_engines.py in libcros.
638    descriptions->push_back(InputMethodDescriptor(
639        "chewing", "Chewing", "us", "zh_TW"));
640    descriptions->push_back(InputMethodDescriptor(
641        "hangul", "Korean", "us", "ko"));
642    descriptions->push_back(InputMethodDescriptor(
643        "m17n:fa:isiri", "isiri (m17n)", "us", "fa"));
644    descriptions->push_back(InputMethodDescriptor(
645        "m17n:he:kbd", "kbd (m17n)", "us", "he"));
646    descriptions->push_back(InputMethodDescriptor(
647        "m17n:ar:kbd", "kbd (m17n)", "us", "ar"));
648    descriptions->push_back(InputMethodDescriptor(
649        "m17n:hi:itrans", "itrans (m17n)", "us", "hi"));
650    descriptions->push_back(InputMethodDescriptor(
651        "m17n:vi:vni", "vni (m17n)", "us", "vi"));
652    descriptions->push_back(InputMethodDescriptor(
653        "m17n:vi:viqr", "viqr (m17n)", "us", "vi"));
654    descriptions->push_back(InputMethodDescriptor(
655        "m17n:vi:tcvn", "tcvn (m17n)", "us", "vi"));
656    descriptions->push_back(InputMethodDescriptor(
657        "m17n:vi:telex", "telex (m17n)", "us", "vi"));
658    descriptions->push_back(InputMethodDescriptor(
659        "m17n:zh:cangjie", "cangjie (m17n)", "us", "zh"));
660    descriptions->push_back(InputMethodDescriptor(
661        "m17n:zh:quick", "quick (m17n)", "us", "zh"));
662    descriptions->push_back(InputMethodDescriptor(
663        "m17n:th:tis820", "tis820 (m17n)", "us", "th"));
664    descriptions->push_back(InputMethodDescriptor(
665        "m17n:th:kesmanee", "kesmanee (m17n)", "us", "th"));
666    descriptions->push_back(InputMethodDescriptor(
667        "m17n:th:pattachote", "pattachote (m17n)", "us", "th"));
668    descriptions->push_back(InputMethodDescriptor(
669        "mozc-jp", "Mozc (Japanese keyboard layout)", "jp", "ja"));
670    descriptions->push_back(InputMethodDescriptor(
671        "mozc", "Mozc (US keyboard layout)", "us", "ja"));
672    descriptions->push_back(InputMethodDescriptor(
673        "mozc-dv", "Mozc (US Dvorak keyboard layout)", "us(dvorak)", "ja"));
674    descriptions->push_back(InputMethodDescriptor(
675        "pinyin", "Pinyin", "us", "zh"));
676    descriptions->push_back(InputMethodDescriptor(
677        "bopomofo", "Bopomofo", "us", "zh"));
678    descriptions->push_back(InputMethodDescriptor(
679        "xkb:us::eng", "USA", "us", "eng"));
680    descriptions->push_back(InputMethodDescriptor(
681        "xkb:us:dvorak:eng", "USA - Dvorak", "us(dvorak)", "eng"));
682    descriptions->push_back(InputMethodDescriptor(
683        "xkb:be::ger", "Belgium", "be", "ger"));
684    descriptions->push_back(InputMethodDescriptor(
685        "xkb:be::nld", "Belgium", "be", "nld"));
686    descriptions->push_back(InputMethodDescriptor(
687        "xkb:be::fra", "Belgium", "be", "fra"));
688    descriptions->push_back(InputMethodDescriptor(
689        "xkb:br::por", "Brazil", "br", "por"));
690    descriptions->push_back(InputMethodDescriptor(
691        "xkb:bg::bul", "Bulgaria", "bg", "bul"));
692    descriptions->push_back(InputMethodDescriptor(
693        "xkb:ca::fra", "Canada", "ca", "fra"));
694    descriptions->push_back(InputMethodDescriptor(
695        "xkb:ca:eng:eng", "Canada - English", "ca(eng)", "eng"));
696    descriptions->push_back(InputMethodDescriptor(
697        "xkb:hr::scr", "Croatia", "hr", "scr"));
698    descriptions->push_back(InputMethodDescriptor(
699        "xkb:cz::cze", "Czechia", "cz", "cze"));
700    descriptions->push_back(InputMethodDescriptor(
701        "xkb:dk::dan", "Denmark", "dk", "dan"));
702    descriptions->push_back(InputMethodDescriptor(
703        "xkb:nl::nld", "Netherlands", "nl", "nld"));
704    descriptions->push_back(InputMethodDescriptor(
705        "xkb:ee::est", "Estonia", "ee", "est"));
706    descriptions->push_back(InputMethodDescriptor(
707        "xkb:fi::fin", "Finland", "fi", "fin"));
708    descriptions->push_back(InputMethodDescriptor(
709        "xkb:fr::fra", "France", "fr", "fra"));
710    descriptions->push_back(InputMethodDescriptor(
711        "xkb:de::ger", "Germany", "de", "ger"));
712    descriptions->push_back(InputMethodDescriptor(
713        "xkb:gr::gre", "Greece", "gr", "gre"));
714    descriptions->push_back(InputMethodDescriptor(
715        "xkb:hu::hun", "Hungary", "hu", "hun"));
716    descriptions->push_back(InputMethodDescriptor(
717        "xkb:it::ita", "Italy", "it", "ita"));
718    descriptions->push_back(InputMethodDescriptor(
719        "xkb:jp::jpn", "Japan", "jp", "jpn"));
720    descriptions->push_back(InputMethodDescriptor(
721        "xkb:lt::lit", "Lithuania", "lt", "lit"));
722    descriptions->push_back(InputMethodDescriptor(
723        "xkb:lv::lav", "Latvia", "lv", "lav"));
724    descriptions->push_back(InputMethodDescriptor(
725        "xkb:no::nor", "Norway", "no", "nor"));
726    descriptions->push_back(InputMethodDescriptor(
727        "xkb:pl::pol", "Poland", "pl", "pol"));
728    descriptions->push_back(InputMethodDescriptor(
729        "xkb:pt::por", "Portugal", "pt", "por"));
730    descriptions->push_back(InputMethodDescriptor(
731        "xkb:ro::rum", "Romania", "ro", "rum"));
732    descriptions->push_back(InputMethodDescriptor(
733        "xkb:ru::rus", "Russia", "ru", "rus"));
734    descriptions->push_back(InputMethodDescriptor(
735        "xkb:rs::srp", "Serbia", "rs", "srp"));
736    descriptions->push_back(InputMethodDescriptor(
737        "xkb:si::slv", "Slovenia", "si", "slv"));
738    descriptions->push_back(InputMethodDescriptor(
739        "xkb:sk::slo", "Slovakia", "sk", "slo"));
740    descriptions->push_back(InputMethodDescriptor(
741        "xkb:es::spa", "Spain", "es", "spa"));
742    descriptions->push_back(InputMethodDescriptor(
743        "xkb:es:cat:cat",
744        "Spain - Catalan variant with middle-dot L", "es(cat)", "cat"));
745    descriptions->push_back(InputMethodDescriptor(
746        "xkb:se::swe", "Sweden", "se", "swe"));
747    descriptions->push_back(InputMethodDescriptor(
748        "xkb:ch::ger", "Switzerland", "ch", "ger"));
749    descriptions->push_back(InputMethodDescriptor(
750        "xkb:ch:fr:fra", "Switzerland - French", "ch(fr)", "fra"));
751    descriptions->push_back(InputMethodDescriptor(
752        "xkb:tr::tur", "Turkey", "tr", "tur"));
753    descriptions->push_back(InputMethodDescriptor(
754        "xkb:ua::ukr", "Ukraine", "ua", "ukr"));
755    descriptions->push_back(InputMethodDescriptor(
756        "xkb:gb:extd:eng", "United Kingdom - Extended - Winkeys", "gb(extd)",
757        "eng"));
758    return descriptions;
759  }
760
761  InputMethodDescriptor previous_input_method_;
762  InputMethodDescriptor current_input_method_;
763  ImePropertyList current_ime_properties_;
764
765  DISALLOW_COPY_AND_ASSIGN(InputMethodLibraryStubImpl);
766};
767
768// static
769InputMethodLibrary* InputMethodLibrary::GetImpl(bool stub) {
770  if (stub)
771    return new InputMethodLibraryStubImpl();
772  else
773    return new InputMethodLibraryImpl();
774}
775
776}  // namespace chromeos
777
778// Allows InvokeLater without adding refcounting. This class is a Singleton and
779// won't be deleted until it's last InvokeLater is run.
780DISABLE_RUNNABLE_METHOD_REFCOUNT(chromeos::InputMethodLibraryImpl);
781