blob: 9b5316f1da56f338474c57627fef23ab16421d1f [file] [log] [blame]
// Copyright (c) 2010 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/chromeos/cros/input_method_library.h"
#include <glib.h>
#include <signal.h>
#include "unicode/uloc.h"
#include "base/basictypes.h"
#include "base/message_loop.h"
#include "base/string_util.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/browser_thread.h"
#include "chrome/browser/chromeos/cros/cros_library.h"
#include "chrome/browser/chromeos/cros/keyboard_library.h"
#include "chrome/browser/chromeos/input_method/input_method_util.h"
#include "chrome/browser/chromeos/language_preferences.h"
#include "chrome/common/notification_observer.h"
#include "chrome/common/notification_registrar.h"
#include "chrome/common/notification_service.h"
namespace {
const char kIBusDaemonPath[] = "/usr/bin/ibus-daemon";
const char kCandidateWindowPath[] = "/opt/google/chrome/candidate_window";
// Finds a property which has |new_prop.key| from |prop_list|, and replaces the
// property with |new_prop|. Returns true if such a property is found.
bool FindAndUpdateProperty(const chromeos::ImeProperty& new_prop,
chromeos::ImePropertyList* prop_list) {
for (size_t i = 0; i < prop_list->size(); ++i) {
chromeos::ImeProperty& prop = prop_list->at(i);
if (prop.key == new_prop.key) {
const int saved_id = prop.selection_item_id;
// Update the list except the radio id. As written in
// chromeos_input_method.h, |prop.selection_item_id| is dummy.
prop = new_prop;
prop.selection_item_id = saved_id;
return true;
}
}
return false;
}
} // namespace
namespace chromeos {
class InputMethodLibraryImpl : public InputMethodLibrary,
public NotificationObserver {
public:
InputMethodLibraryImpl()
: input_method_status_connection_(NULL),
previous_input_method_("", "", "", ""),
current_input_method_("", "", "", ""),
should_launch_ime_(false),
ime_connected_(false),
defer_ime_startup_(false),
enable_auto_ime_shutdown_(true),
should_change_input_method_(false),
ibus_daemon_process_id_(0),
candidate_window_process_id_(0) {
scoped_ptr<InputMethodDescriptors> input_method_descriptors(
CreateFallbackInputMethodDescriptors());
current_input_method_ = input_method_descriptors->at(0);
if (CrosLibrary::Get()->EnsureLoaded()) {
current_input_method_id_ = chromeos::GetHardwareKeyboardLayoutName();
}
// Observe APP_EXITING to stop input method processes gracefully.
// Note that even if we fail to stop input method processes from
// Chrome in case of a sudden crash, we have a way to do it from an
// upstart script. See crosbug.com/6515 and crosbug.com/6995 for
// details.
notification_registrar_.Add(this, NotificationType::APP_EXITING,
NotificationService::AllSources());
}
~InputMethodLibraryImpl() {
}
void AddObserver(Observer* observer) {
observers_.AddObserver(observer);
}
void RemoveObserver(Observer* observer) {
observers_.RemoveObserver(observer);
}
InputMethodDescriptors* GetActiveInputMethods() {
chromeos::InputMethodDescriptors* result = NULL;
// The connection does not need to be alive, but it does need to be created.
if (EnsureLoadedAndStarted()) {
result = chromeos::GetActiveInputMethods(input_method_status_connection_);
}
if (!result || result->empty()) {
result = CreateFallbackInputMethodDescriptors();
}
return result;
}
size_t GetNumActiveInputMethods() {
scoped_ptr<InputMethodDescriptors> input_methods(GetActiveInputMethods());
return input_methods->size();
}
InputMethodDescriptors* GetSupportedInputMethods() {
InputMethodDescriptors* result = NULL;
// The connection does not need to be alive, but it does need to be created.
if (EnsureLoadedAndStarted()) {
result = chromeos::GetSupportedInputMethods(
input_method_status_connection_);
}
if (!result || result->empty()) {
result = CreateFallbackInputMethodDescriptors();
}
return result;
}
void ChangeInputMethod(const std::string& input_method_id) {
current_input_method_id_ = input_method_id;
if (EnsureLoadedAndStarted()) {
if (input_method_id != chromeos::GetHardwareKeyboardLayoutName()) {
StartInputMethodProcesses();
}
ChangeInputMethodInternal(input_method_id);
}
}
void SetImePropertyActivated(const std::string& key, bool activated) {
DCHECK(!key.empty());
if (EnsureLoadedAndStarted()) {
chromeos::SetImePropertyActivated(
input_method_status_connection_, key.c_str(), activated);
}
}
bool InputMethodIsActivated(const std::string& input_method_id) {
scoped_ptr<InputMethodDescriptors> active_input_method_descriptors(
GetActiveInputMethods());
for (size_t i = 0; i < active_input_method_descriptors->size(); ++i) {
if (active_input_method_descriptors->at(i).id == input_method_id) {
return true;
}
}
return false;
}
bool GetImeConfig(const char* section, const char* config_name,
ImeConfigValue* out_value) {
bool success = false;
if (EnsureLoadedAndStarted()) {
success = chromeos::GetImeConfig(input_method_status_connection_,
section, config_name, out_value);
}
return success;
}
bool SetImeConfig(const char* section, const char* config_name,
const ImeConfigValue& value) {
MaybeStartOrStopInputMethodProcesses(section, config_name, value);
const ConfigKeyType key = std::make_pair(section, config_name);
current_config_values_[key] = value;
if (ime_connected_) {
pending_config_requests_[key] = value;
FlushImeConfig();
}
return pending_config_requests_.empty();
}
virtual const InputMethodDescriptor& previous_input_method() const {
return previous_input_method_;
}
virtual const InputMethodDescriptor& current_input_method() const {
return current_input_method_;
}
virtual const ImePropertyList& current_ime_properties() const {
return current_ime_properties_;
}
private:
// Starts or stops the input method processes based on the current state.
void MaybeStartOrStopInputMethodProcesses(
const char* section,
const char* config_name,
const ImeConfigValue& value) {
if (!strcmp(language_prefs::kGeneralSectionName, section) &&
!strcmp(language_prefs::kPreloadEnginesConfigName, config_name)) {
if (EnsureLoadedAndStarted()) {
// If there are no input methods other than one for the hardware
// keyboard, we'll stop the input method processes.
if (value.type == ImeConfigValue::kValueTypeStringList &&
value.string_list_value.size() == 1 &&
value.string_list_value[0] ==
chromeos::GetHardwareKeyboardLayoutName()) {
if (enable_auto_ime_shutdown_)
StopInputMethodProcesses();
} else if (!defer_ime_startup_) {
StartInputMethodProcesses();
}
chromeos::SetActiveInputMethods(input_method_status_connection_, value);
}
}
}
// Changes the current input method to |input_method_id|. If the id is not in
// the preload_engine list, this function changes the current method to the
// first preloaded engine. Returns true if the current engine is switched to
// |input_method_id| or the first one.
bool ChangeInputMethodInternal(const std::string& input_method_id) {
DCHECK(EnsureLoadedAndStarted());
std::string input_method_id_to_switch = input_method_id;
if (!InputMethodIsActivated(input_method_id)) {
// This path might be taken if prefs::kLanguageCurrentInputMethod (NOT
// synced with cloud) and kLanguagePreloadEngines (synced with cloud) are
// mismatched. e.g. the former is 'xkb:us::eng' and the latter (on the
// sync server) is 'xkb:jp::jpn,mozc'.
scoped_ptr<InputMethodDescriptors> input_methods(GetActiveInputMethods());
DCHECK(!input_methods->empty());
if (!input_methods->empty()) {
input_method_id_to_switch = input_methods->at(0).id;
LOG(INFO) << "Can't change the current input method to "
<< input_method_id << " since the engine is not preloaded. "
<< "Switch to " << input_method_id_to_switch << " instead.";
}
}
if (chromeos::ChangeInputMethod(input_method_status_connection_,
input_method_id_to_switch.c_str())) {
return true;
}
// Not reached.
LOG(ERROR) << "Can't switch input method to " << input_method_id_to_switch;
return false;
}
// Flushes the input method config data. The config data is queued up in
// |pending_config_requests_| until the config backend (ibus-memconf)
// starts. Since there is no good way to get notified when the config
// backend starts, we use a timer to periodically attempt to send the
// config data to the config backend.
void FlushImeConfig() {
bool active_input_methods_are_changed = false;
if (EnsureLoadedAndStarted()) {
InputMethodConfigRequests::iterator iter =
pending_config_requests_.begin();
while (iter != pending_config_requests_.end()) {
const std::string& section = iter->first.first;
const std::string& config_name = iter->first.second;
const ImeConfigValue& value = iter->second;
if (chromeos::SetImeConfig(input_method_status_connection_,
section.c_str(),
config_name.c_str(),
value)) {
// Check if it's a change in active input methods.
if (config_name == language_prefs::kPreloadEnginesConfigName) {
active_input_methods_are_changed = true;
}
// Successfully sent. Remove the command and proceed to the next one.
pending_config_requests_.erase(iter++);
} else {
// If SetImeConfig() fails, subsequent calls will likely fail.
break;
}
}
if (pending_config_requests_.empty()) {
// Calls to ChangeInputMethod() will fail if the input method has not
// yet been added to preload_engines. As such, the call is deferred
// until after all config values have been sent to the IME process.
if (should_change_input_method_) {
ChangeInputMethodInternal(current_input_method_id_);
should_change_input_method_ = false;
active_input_methods_are_changed = true;
}
}
}
if (pending_config_requests_.empty()) {
timer_.Stop(); // no-op if it's not running.
} else if (!timer_.IsRunning()) {
// Flush is not completed. Start a timer if it's not yet running.
static const int64 kTimerIntervalInMsec = 100;
timer_.Start(base::TimeDelta::FromMilliseconds(kTimerIntervalInMsec),
this, &InputMethodLibraryImpl::FlushImeConfig);
}
if (active_input_methods_are_changed) {
const size_t num_active_input_methods = GetNumActiveInputMethods();
FOR_EACH_OBSERVER(Observer, observers_,
ActiveInputMethodsChanged(this,
current_input_method_,
num_active_input_methods));
}
}
static void InputMethodChangedHandler(
void* object,
const chromeos::InputMethodDescriptor& current_input_method) {
// The handler is called when the input method method change is
// notified via a DBus connection. Since the DBus notificatiosn are
// handled in the UI thread, we can assume that this functionalways
// runs on the UI thread, but just in case.
if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) {
LOG(ERROR) << "Not on UI thread";
return;
}
InputMethodLibraryImpl* input_method_library =
static_cast<InputMethodLibraryImpl*>(object);
input_method_library->ChangeCurrentInputMethod(current_input_method);
}
static void RegisterPropertiesHandler(
void* object, const ImePropertyList& prop_list) {
// See comments in InputMethodChangedHandler.
if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) {
LOG(ERROR) << "Not on UI thread";
return;
}
InputMethodLibraryImpl* input_method_library =
static_cast<InputMethodLibraryImpl*>(object);
input_method_library->RegisterProperties(prop_list);
}
static void UpdatePropertyHandler(
void* object, const ImePropertyList& prop_list) {
// See comments in InputMethodChangedHandler.
if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) {
LOG(ERROR) << "Not on UI thread";
return;
}
InputMethodLibraryImpl* input_method_library =
static_cast<InputMethodLibraryImpl*>(object);
input_method_library->UpdateProperty(prop_list);
}
static void ConnectionChangeHandler(void* object, bool connected) {
// See comments in InputMethodChangedHandler.
if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) {
LOG(ERROR) << "Not on UI thread";
return;
}
InputMethodLibraryImpl* input_method_library =
static_cast<InputMethodLibraryImpl*>(object);
input_method_library->ime_connected_ = connected;
if (connected) {
input_method_library->pending_config_requests_.clear();
input_method_library->pending_config_requests_.insert(
input_method_library->current_config_values_.begin(),
input_method_library->current_config_values_.end());
input_method_library->should_change_input_method_ = true;
input_method_library->FlushImeConfig();
} else {
// Stop attempting to resend config data, since it will continue to fail.
input_method_library->timer_.Stop(); // no-op if it's not running.
}
}
bool EnsureStarted() {
if (!input_method_status_connection_) {
input_method_status_connection_ = chromeos::MonitorInputMethodStatus(
this,
&InputMethodChangedHandler,
&RegisterPropertiesHandler,
&UpdatePropertyHandler,
&ConnectionChangeHandler);
}
return true;
}
bool EnsureLoadedAndStarted() {
return CrosLibrary::Get()->EnsureLoaded() &&
EnsureStarted();
}
void ChangeCurrentInputMethod(const InputMethodDescriptor& new_input_method) {
// Change the keyboard layout to a preferred layout for the input method.
CrosLibrary::Get()->GetKeyboardLibrary()->SetCurrentKeyboardLayoutByName(
new_input_method.keyboard_layout);
if (current_input_method_.id != new_input_method.id) {
previous_input_method_ = current_input_method_;
current_input_method_ = new_input_method;
}
const size_t num_active_input_methods = GetNumActiveInputMethods();
FOR_EACH_OBSERVER(Observer, observers_,
InputMethodChanged(this,
previous_input_method_,
current_input_method_,
num_active_input_methods));
// Ask the first observer to update preferences. We should not ask every
// observer to do so. Otherwise, we'll end up updating preferences many
// times when many observers are attached (ex. many windows are opened),
// which is unnecessary and expensive.
ObserverListBase<Observer>::Iterator it(observers_);
Observer* first_observer = it.GetNext();
if (first_observer) {
first_observer->PreferenceUpdateNeeded(this,
previous_input_method_,
current_input_method_);
}
}
void RegisterProperties(const ImePropertyList& prop_list) {
// |prop_list| might be empty. This means "clear all properties."
current_ime_properties_ = prop_list;
}
void StartInputMethodProcesses() {
should_launch_ime_ = true;
MaybeLaunchInputMethodProcesses();
}
void UpdateProperty(const ImePropertyList& prop_list) {
for (size_t i = 0; i < prop_list.size(); ++i) {
FindAndUpdateProperty(prop_list[i], &current_ime_properties_);
}
}
// Launches an input method procsess specified by the given command
// line. On success, returns true and stores the process ID in
// |process_id|. Otherwise, returns false, and the contents of
// |process_id| is untouched. OnImeShutdown will be called when the
// process terminates.
bool LaunchInputMethodProcess(const std::string& command_line,
int* process_id) {
GError *error = NULL;
gchar **argv = NULL;
gint argc = NULL;
// TODO(zork): export "LD_PRELOAD=/usr/lib/libcrash.so"
if (!g_shell_parse_argv(command_line.c_str(), &argc, &argv, &error)) {
LOG(ERROR) << "Could not parse command: " << error->message;
g_error_free(error);
return false;
}
int pid = 0;
const GSpawnFlags kFlags = G_SPAWN_DO_NOT_REAP_CHILD;
const gboolean result = g_spawn_async(NULL, argv, NULL,
kFlags, NULL, NULL,
&pid, &error);
g_strfreev(argv);
if (!result) {
LOG(ERROR) << "Could not launch: " << command_line << ": "
<< error->message;
g_error_free(error);
return false;
}
g_child_watch_add(pid, reinterpret_cast<GChildWatchFunc>(OnImeShutdown),
this);
*process_id = pid;
return true;
}
// Launches input method processes if these are not yet running.
void MaybeLaunchInputMethodProcesses() {
if (!should_launch_ime_) {
return;
}
if (ibus_daemon_process_id_ == 0) {
// TODO(zork): Send output to /var/log/ibus.log
const std::string ibus_daemon_command_line =
StringPrintf("%s --panel=disable --cache=none --restart --replace",
kIBusDaemonPath);
if (!LaunchInputMethodProcess(ibus_daemon_command_line,
&ibus_daemon_process_id_)) {
// On failure, we should not attempt to launch candidate_window.
return;
}
}
if (candidate_window_process_id_ == 0) {
// Pass the UI language info to candidate_window via --lang flag.
const std::string candidate_window_command_line =
StringPrintf("%s --lang=%s", kCandidateWindowPath,
g_browser_process->GetApplicationLocale().c_str());
if (!LaunchInputMethodProcess(candidate_window_command_line,
&candidate_window_process_id_)) {
// Return here just in case we add more code below.
return;
}
}
}
static void OnImeShutdown(int pid,
int status,
InputMethodLibraryImpl* library) {
g_spawn_close_pid(pid);
if (library->ibus_daemon_process_id_ == pid) {
library->ibus_daemon_process_id_ = 0;
} else if (library->candidate_window_process_id_ == pid) {
library->candidate_window_process_id_ = 0;
}
// Restart input method processes if needed.
library->MaybeLaunchInputMethodProcesses();
}
void StopInputMethodProcesses() {
should_launch_ime_ = false;
if (ibus_daemon_process_id_) {
const std::string xkb_engine_name =
chromeos::GetHardwareKeyboardLayoutName();
// We should not use chromeos::ChangeInputMethod() here since without the
// ibus-daemon process, ChangeCurrentInputMethod() callback function which
// actually changes the XKB layout will not be called.
CrosLibrary::Get()->GetKeyboardLibrary()->SetCurrentKeyboardLayoutByName(
chromeos::input_method::GetKeyboardLayoutName(xkb_engine_name));
kill(ibus_daemon_process_id_, SIGTERM);
ibus_daemon_process_id_ = 0;
}
if (candidate_window_process_id_) {
kill(candidate_window_process_id_, SIGTERM);
candidate_window_process_id_ = 0;
}
}
void SetDeferImeStartup(bool defer) {
VLOG(1) << "Setting DeferImeStartup to " << defer;
defer_ime_startup_ = defer;
}
void SetEnableAutoImeShutdown(bool enable) {
enable_auto_ime_shutdown_ = enable;
}
// NotificationObserver implementation:
void Observe(NotificationType type,
const NotificationSource& source,
const NotificationDetails& details) {
// Stop the input processes on browser shutdown.
if (type.value == NotificationType::APP_EXITING) {
StopInputMethodProcesses();
}
}
// A reference to the language api, to allow callbacks when the input method
// status changes.
InputMethodStatusConnection* input_method_status_connection_;
ObserverList<Observer> observers_;
// The input method which was/is selected.
InputMethodDescriptor previous_input_method_;
InputMethodDescriptor current_input_method_;
// The input method properties which the current input method uses. The list
// might be empty when no input method is used.
ImePropertyList current_ime_properties_;
typedef std::pair<std::string, std::string> ConfigKeyType;
typedef std::map<ConfigKeyType, ImeConfigValue> InputMethodConfigRequests;
// SetImeConfig requests that are not yet completed.
// Use a map to queue config requests, so we only send the last request for
// the same config key (i.e. we'll discard ealier requests for the same
// config key). As we discard old requests for the same config key, the order
// of requests doesn't matter, so it's safe to use a map.
InputMethodConfigRequests pending_config_requests_;
// Values that have been set via SetImeConfig(). We keep a copy available to
// resend if the ime restarts and loses its state.
InputMethodConfigRequests current_config_values_;
// A timer for retrying to send |pendning_config_commands_| to the input
// method config daemon.
base::OneShotTimer<InputMethodLibraryImpl> timer_;
// This is used to register this object to APP_EXITING notification.
NotificationRegistrar notification_registrar_;
// True if we should launch the input method processes.
bool should_launch_ime_;
// True if the connection to the IBus daemon is alive.
bool ime_connected_;
// If true, we'll defer the startup until a non-default method is
// activated.
bool defer_ime_startup_;
bool enable_auto_ime_shutdown_;
// The ID of the current input method (ex. "mozc").
std::string current_input_method_id_;
// True if we should change the input method once the queue of the
// pending config requests becomes empty.
bool should_change_input_method_;
// The process id of the IBus daemon. 0 if it's not running. The process
// ID 0 is not used in Linux, hence it's safe to use 0 for this purpose.
int ibus_daemon_process_id_;
// The process id of the candidate window. 0 if it's not running.
int candidate_window_process_id_;
DISALLOW_COPY_AND_ASSIGN(InputMethodLibraryImpl);
};
InputMethodLibraryImpl::Observer::~Observer() {}
class InputMethodLibraryStubImpl : public InputMethodLibrary {
public:
InputMethodLibraryStubImpl()
: previous_input_method_("", "", "", ""),
current_input_method_("", "", "", "") {
}
~InputMethodLibraryStubImpl() {}
void AddObserver(Observer* observer) {}
void RemoveObserver(Observer* observer) {}
InputMethodDescriptors* GetActiveInputMethods() {
return CreateRealisticInputMethodDescriptors();
}
size_t GetNumActiveInputMethods() {
scoped_ptr<InputMethodDescriptors> descriptors(
CreateRealisticInputMethodDescriptors());
return descriptors->size();
}
InputMethodDescriptors* GetSupportedInputMethods() {
return CreateRealisticInputMethodDescriptors();
}
void ChangeInputMethod(const std::string& input_method_id) {}
void SetImePropertyActivated(const std::string& key, bool activated) {}
bool InputMethodIsActivated(const std::string& input_method_id) {
return true;
}
bool GetImeConfig(const char* section,
const char* config_name,
ImeConfigValue* out_value) {
return false;
}
bool SetImeConfig(const char* section,
const char* config_name,
const ImeConfigValue& value) {
return false;
}
virtual const InputMethodDescriptor& previous_input_method() const {
return previous_input_method_;
}
virtual const InputMethodDescriptor& current_input_method() const {
return current_input_method_;
}
virtual const ImePropertyList& current_ime_properties() const {
return current_ime_properties_;
}
virtual void StartInputMethodProcesses() {}
virtual void StopInputMethodProcesses() {}
virtual void SetDeferImeStartup(bool defer) {}
virtual void SetEnableAutoImeShutdown(bool enable) {}
private:
// Creates realistic input method descriptors that can be used for
// testing Chrome OS version of chrome on regular Linux desktops.
InputMethodDescriptors* CreateRealisticInputMethodDescriptors() {
InputMethodDescriptors* descriptions = new InputMethodDescriptors;
// The list is created from output of gen_engines.py in libcros.
descriptions->push_back(InputMethodDescriptor(
"chewing", "Chewing", "us", "zh_TW"));
descriptions->push_back(InputMethodDescriptor(
"hangul", "Korean", "us", "ko"));
descriptions->push_back(InputMethodDescriptor(
"m17n:fa:isiri", "isiri (m17n)", "us", "fa"));
descriptions->push_back(InputMethodDescriptor(
"m17n:he:kbd", "kbd (m17n)", "us", "he"));
descriptions->push_back(InputMethodDescriptor(
"m17n:ar:kbd", "kbd (m17n)", "us", "ar"));
descriptions->push_back(InputMethodDescriptor(
"m17n:hi:itrans", "itrans (m17n)", "us", "hi"));
descriptions->push_back(InputMethodDescriptor(
"m17n:vi:vni", "vni (m17n)", "us", "vi"));
descriptions->push_back(InputMethodDescriptor(
"m17n:vi:viqr", "viqr (m17n)", "us", "vi"));
descriptions->push_back(InputMethodDescriptor(
"m17n:vi:tcvn", "tcvn (m17n)", "us", "vi"));
descriptions->push_back(InputMethodDescriptor(
"m17n:vi:telex", "telex (m17n)", "us", "vi"));
descriptions->push_back(InputMethodDescriptor(
"m17n:zh:cangjie", "cangjie (m17n)", "us", "zh"));
descriptions->push_back(InputMethodDescriptor(
"m17n:zh:quick", "quick (m17n)", "us", "zh"));
descriptions->push_back(InputMethodDescriptor(
"m17n:th:tis820", "tis820 (m17n)", "us", "th"));
descriptions->push_back(InputMethodDescriptor(
"m17n:th:kesmanee", "kesmanee (m17n)", "us", "th"));
descriptions->push_back(InputMethodDescriptor(
"m17n:th:pattachote", "pattachote (m17n)", "us", "th"));
descriptions->push_back(InputMethodDescriptor(
"mozc-jp", "Mozc (Japanese keyboard layout)", "jp", "ja"));
descriptions->push_back(InputMethodDescriptor(
"mozc", "Mozc (US keyboard layout)", "us", "ja"));
descriptions->push_back(InputMethodDescriptor(
"mozc-dv", "Mozc (US Dvorak keyboard layout)", "us(dvorak)", "ja"));
descriptions->push_back(InputMethodDescriptor(
"pinyin", "Pinyin", "us", "zh"));
descriptions->push_back(InputMethodDescriptor(
"bopomofo", "Bopomofo", "us", "zh"));
descriptions->push_back(InputMethodDescriptor(
"xkb:us::eng", "USA", "us", "eng"));
descriptions->push_back(InputMethodDescriptor(
"xkb:us:dvorak:eng", "USA - Dvorak", "us(dvorak)", "eng"));
descriptions->push_back(InputMethodDescriptor(
"xkb:be::ger", "Belgium", "be", "ger"));
descriptions->push_back(InputMethodDescriptor(
"xkb:be::nld", "Belgium", "be", "nld"));
descriptions->push_back(InputMethodDescriptor(
"xkb:be::fra", "Belgium", "be", "fra"));
descriptions->push_back(InputMethodDescriptor(
"xkb:br::por", "Brazil", "br", "por"));
descriptions->push_back(InputMethodDescriptor(
"xkb:bg::bul", "Bulgaria", "bg", "bul"));
descriptions->push_back(InputMethodDescriptor(
"xkb:ca::fra", "Canada", "ca", "fra"));
descriptions->push_back(InputMethodDescriptor(
"xkb:ca:eng:eng", "Canada - English", "ca(eng)", "eng"));
descriptions->push_back(InputMethodDescriptor(
"xkb:hr::scr", "Croatia", "hr", "scr"));
descriptions->push_back(InputMethodDescriptor(
"xkb:cz::cze", "Czechia", "cz", "cze"));
descriptions->push_back(InputMethodDescriptor(
"xkb:dk::dan", "Denmark", "dk", "dan"));
descriptions->push_back(InputMethodDescriptor(
"xkb:nl::nld", "Netherlands", "nl", "nld"));
descriptions->push_back(InputMethodDescriptor(
"xkb:ee::est", "Estonia", "ee", "est"));
descriptions->push_back(InputMethodDescriptor(
"xkb:fi::fin", "Finland", "fi", "fin"));
descriptions->push_back(InputMethodDescriptor(
"xkb:fr::fra", "France", "fr", "fra"));
descriptions->push_back(InputMethodDescriptor(
"xkb:de::ger", "Germany", "de", "ger"));
descriptions->push_back(InputMethodDescriptor(
"xkb:gr::gre", "Greece", "gr", "gre"));
descriptions->push_back(InputMethodDescriptor(
"xkb:hu::hun", "Hungary", "hu", "hun"));
descriptions->push_back(InputMethodDescriptor(
"xkb:it::ita", "Italy", "it", "ita"));
descriptions->push_back(InputMethodDescriptor(
"xkb:jp::jpn", "Japan", "jp", "jpn"));
descriptions->push_back(InputMethodDescriptor(
"xkb:lt::lit", "Lithuania", "lt", "lit"));
descriptions->push_back(InputMethodDescriptor(
"xkb:lv::lav", "Latvia", "lv", "lav"));
descriptions->push_back(InputMethodDescriptor(
"xkb:no::nor", "Norway", "no", "nor"));
descriptions->push_back(InputMethodDescriptor(
"xkb:pl::pol", "Poland", "pl", "pol"));
descriptions->push_back(InputMethodDescriptor(
"xkb:pt::por", "Portugal", "pt", "por"));
descriptions->push_back(InputMethodDescriptor(
"xkb:ro::rum", "Romania", "ro", "rum"));
descriptions->push_back(InputMethodDescriptor(
"xkb:ru::rus", "Russia", "ru", "rus"));
descriptions->push_back(InputMethodDescriptor(
"xkb:rs::srp", "Serbia", "rs", "srp"));
descriptions->push_back(InputMethodDescriptor(
"xkb:si::slv", "Slovenia", "si", "slv"));
descriptions->push_back(InputMethodDescriptor(
"xkb:sk::slo", "Slovakia", "sk", "slo"));
descriptions->push_back(InputMethodDescriptor(
"xkb:es::spa", "Spain", "es", "spa"));
descriptions->push_back(InputMethodDescriptor(
"xkb:es:cat:cat",
"Spain - Catalan variant with middle-dot L", "es(cat)", "cat"));
descriptions->push_back(InputMethodDescriptor(
"xkb:se::swe", "Sweden", "se", "swe"));
descriptions->push_back(InputMethodDescriptor(
"xkb:ch::ger", "Switzerland", "ch", "ger"));
descriptions->push_back(InputMethodDescriptor(
"xkb:ch:fr:fra", "Switzerland - French", "ch(fr)", "fra"));
descriptions->push_back(InputMethodDescriptor(
"xkb:tr::tur", "Turkey", "tr", "tur"));
descriptions->push_back(InputMethodDescriptor(
"xkb:ua::ukr", "Ukraine", "ua", "ukr"));
descriptions->push_back(InputMethodDescriptor(
"xkb:gb:extd:eng", "United Kingdom - Extended - Winkeys", "gb(extd)",
"eng"));
return descriptions;
}
InputMethodDescriptor previous_input_method_;
InputMethodDescriptor current_input_method_;
ImePropertyList current_ime_properties_;
DISALLOW_COPY_AND_ASSIGN(InputMethodLibraryStubImpl);
};
// static
InputMethodLibrary* InputMethodLibrary::GetImpl(bool stub) {
if (stub)
return new InputMethodLibraryStubImpl();
else
return new InputMethodLibraryImpl();
}
} // namespace chromeos
// Allows InvokeLater without adding refcounting. This class is a Singleton and
// won't be deleted until it's last InvokeLater is run.
DISABLE_RUNNABLE_METHOD_REFCOUNT(chromeos::InputMethodLibraryImpl);