| Index: chromeos_language.cc
|
| diff --git a/chromeos_language.cc b/chromeos_language.cc
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..8aed34f10084f3dc5f5d032519ffd05a0b4b95b7
|
| --- /dev/null
|
| +++ b/chromeos_language.cc
|
| @@ -0,0 +1,342 @@
|
| +// Copyright (c) 2009 The Chromium OS 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 "chromeos_language.h"
|
| +
|
| +#include <base/logging.h>
|
| +#include <ibus.h>
|
| +
|
| +#include <algorithm> // for std::sort.
|
| +
|
| +#include "chromeos/dbus/dbus.h"
|
| +#include "chromeos/glib/object.h"
|
| +
|
| +namespace {
|
| +
|
| +const char kCandidateWindowService[] = "org.freedesktop.IBus.Panel";
|
| +const char kCandidateWindowObjectPath[] = "/org/chromium/Chrome/LanguageBar";
|
| +const char kCandidateWindowInterface[] = "org.freedesktop.IBus.Panel";
|
| +
|
| +// Copies IME names in |engines| to |out|.
|
| +void AddIMELanguages(const GList* engines, chromeos::InputLanguageList* out) {
|
| + DCHECK(out);
|
| + for (; engines; engines = g_list_next(engines)) {
|
| + IBusEngineDesc *engine_desc = IBUS_ENGINE_DESC(engines->data);
|
| + out->push_back(chromeos::InputLanguage(
|
| + chromeos::LANGUAGE_CATEGORY_IME,
|
| + engine_desc->name, engine_desc->longname, engine_desc->icon));
|
| + g_object_unref(engine_desc);
|
| + }
|
| +}
|
| +
|
| +// Copies XKB layout names in (TBD) to |out|.
|
| +void AddXKBLayouts(chromeos::InputLanguageList* out) {
|
| + DCHECK(out);
|
| + // TODO(yusukes): implement this.
|
| + out->push_back(chromeos::InputLanguage(
|
| + chromeos::LANGUAGE_CATEGORY_XKB,
|
| + kFallbackXKBId,
|
| + kFallbackXKBDisplayName,
|
| + "" /* no icon */)); // mock
|
| +}
|
| +
|
| +} // namespace
|
| +
|
| +namespace chromeos {
|
| +
|
| +// A class that holds IBus and DBus connections.
|
| +class LanguageStatusConnection {
|
| + public:
|
| + LanguageStatusConnection(LanguageStatusMonitorFunction monitor_function,
|
| + void* language_library)
|
| + : monitor_function_(monitor_function),
|
| + language_library_(language_library),
|
| + ibus_(NULL),
|
| + dbus_focus_in_(NULL),
|
| + dbus_focus_out_(NULL),
|
| + dbus_state_changed_(NULL),
|
| + input_context_path_("") {
|
| + DCHECK(monitor_function_);
|
| + DCHECK(language_library_);
|
| + }
|
| +
|
| + ~LanguageStatusConnection() {
|
| + // Close IBus and DBus connections.
|
| + if (ibus_) {
|
| + g_object_unref(ibus_);
|
| + }
|
| + if (dbus_focus_in_) {
|
| + dbus::Disconnect(dbus_focus_in_);
|
| + }
|
| + if (dbus_focus_out_) {
|
| + dbus::Disconnect(dbus_focus_out_);
|
| + }
|
| + if (dbus_state_changed_) {
|
| + dbus::Disconnect(dbus_state_changed_);
|
| + }
|
| + }
|
| +
|
| + // Initializes IBus and DBus connections.
|
| + bool Init() {
|
| + // Establish IBus connection between ibus-daemon to retrieve the list of
|
| + // available IME engines, change the current IME engine, and so on.
|
| + ibus_init();
|
| + ibus_ = ibus_bus_new();
|
| +
|
| + // Check the IBus connection status.
|
| + if (!ibus_) {
|
| + LOG(ERROR) << "ibus_bus_new() failed";
|
| + return false;
|
| + }
|
| + if (!ibus_bus_is_connected(ibus_)) {
|
| + LOG(ERROR) << "ibus_bus_is_connected() failed";
|
| + return false;
|
| + }
|
| +
|
| + // Establish a DBus connection between the candidate_window process for
|
| + // Chromium OS to handle "FocusIn", "FocusOut", and "StateChanged" signals
|
| + // from the process.
|
| + const char* address = ibus_get_address();
|
| + dbus::BusConnection dbus(dbus::GetPrivateBusConnection(address));
|
| + LOG(INFO) << "Established private D-Bus connection to: '" << address << "'";
|
| +
|
| + const bool kConnectToNameOwner = true;
|
| + // TODO(yusukes): dbus::Proxy instantiation might fail (and abort due to
|
| + // DCHECK failure) when candidate_window process does not exist yet.
|
| + // Would be better to add "bool dbus::Proxy::Init()" or something like that
|
| + // to handle such case?
|
| + dbus::Proxy candidate_window(dbus,
|
| + kCandidateWindowService,
|
| + kCandidateWindowObjectPath,
|
| + kCandidateWindowInterface,
|
| + kConnectToNameOwner);
|
| +
|
| + if (!candidate_window) {
|
| + LOG(ERROR) << "Can't construct proxy for the candidate window. "
|
| + << "candidate window is not running?";
|
| + return false;
|
| + }
|
| +
|
| + dbus_focus_in_ = dbus::Monitor(candidate_window,
|
| + "FocusIn",
|
| + &LanguageStatusConnection::FocusIn,
|
| + this);
|
| + dbus_focus_out_ = dbus::Monitor(candidate_window,
|
| + "FocusOut",
|
| + &LanguageStatusConnection::FocusOut,
|
| + this);
|
| + dbus_state_changed_ = dbus::Monitor(candidate_window,
|
| + "StateChanged",
|
| + &LanguageStatusConnection::StateChanged,
|
| + this);
|
| +
|
| + // TODO(yusukes): Investigate what happens if IBus/DBus connections are
|
| + // suddenly closed.
|
| + // TODO(yusukes): Investigate what happens if candidate_window process is
|
| + // restarted. I'm not sure but we should use dbus_g_proxy_new_for_name(),
|
| + // not dbus_g_proxy_new_for_name_owner()?
|
| +
|
| + return true;
|
| + }
|
| +
|
| + // Called by cros API ChromeOSGetLanguages() and returns a list of IMEs and
|
| + // XKB layouts that are currently available.
|
| + InputLanguageList* GetLanguages() {
|
| + GList* engines = ibus_bus_list_active_engines(ibus_);
|
| + if (!engines) {
|
| + // IBus connection is broken?
|
| + LOG(ERROR) << "ibus_bus_list_active_engines() failed.";
|
| + return NULL;
|
| + }
|
| + InputLanguageList* language_list = new InputLanguageList;
|
| + AddIMELanguages(engines, language_list);
|
| + AddXKBLayouts(language_list);
|
| + std::sort(language_list->begin(), language_list->end());
|
| +
|
| + g_list_free(engines);
|
| + return language_list;
|
| + }
|
| +
|
| + // Called by cros API ChromeOSChangeLanguage().
|
| + void SwitchXKB(const char* name) {
|
| + IBusInputContext* context
|
| + = ibus_input_context_get_input_context(input_context_path_.c_str(),
|
| + ibus_bus_get_connection(ibus_));
|
| + ibus_input_context_disable(context);
|
| + g_object_unref(context);
|
| + // TODO(yusukes): implement XKB switching.
|
| + UpdateUI();
|
| + }
|
| +
|
| + // Called by cros API ChromeOSChangeLanguage() as well.
|
| + void SwitchIME(const char* name) {
|
| + if (input_context_path_.empty()) {
|
| + LOG(ERROR) << "Input context is unknown";
|
| + return;
|
| + }
|
| +
|
| + IBusInputContext* context
|
| + = ibus_input_context_get_input_context(input_context_path_.c_str(),
|
| + ibus_bus_get_connection(ibus_));
|
| + ibus_input_context_set_engine(context, name);
|
| + g_object_unref(context);
|
| + UpdateUI();
|
| + }
|
| +
|
| + private:
|
| + // Handles "FocusIn" signal from candidate_window.
|
| + static void FocusIn(void* object, const char* input_context_path) {
|
| + DCHECK(input_context_path) << "NULL context passed";
|
| + DLOG(INFO) << "FocusIn: " << input_context_path;
|
| + DCHECK(object);
|
| +
|
| + // Remember the current ic path.
|
| + LanguageStatusConnection* self
|
| + = static_cast<LanguageStatusConnection*>(object);
|
| + self->input_context_path_ = input_context_path;
|
| + self->UpdateUI(); // This is necessary since IME status is held per ic.
|
| + }
|
| +
|
| + // Handles "FocusOut" signal from candidate_window.
|
| + static void FocusOut(void* object, const char* input_context_path) {
|
| + DCHECK(input_context_path) << "NULL context passed";
|
| + DLOG(INFO) << "FocusOut: " << input_context_path;
|
| + DCHECK(object);
|
| + }
|
| +
|
| + // Handles "StateChanged" signal from candidate_window process.
|
| + static void StateChanged(void* object, const char* dummy) {
|
| + // TODO(yusukes): Modify common/chromeos/dbus/dbus.h so that we can handle
|
| + // signals without argument. Then remove the |dummy|.
|
| + DLOG(INFO) << "StateChanged";
|
| + DCHECK(object);
|
| + LanguageStatusConnection* self
|
| + = static_cast<LanguageStatusConnection*>(object);
|
| + self->UpdateUI();
|
| + }
|
| +
|
| + // Retrieve IME/XKB status and notify them to the UI.
|
| + void UpdateUI() {
|
| + if (input_context_path_.empty()) {
|
| + LOG(ERROR) << "Input context is unknown";
|
| + return;
|
| + }
|
| +
|
| + IBusInputContext* context
|
| + = ibus_input_context_get_input_context(
|
| + input_context_path_.c_str(), ibus_bus_get_connection(ibus_));
|
| +
|
| + InputLanguage current_language;
|
| + const bool ime_is_enabled = ibus_input_context_is_enabled(context);
|
| + if (ime_is_enabled) {
|
| + DLOG(INFO) << "IME is active";
|
| + // Set IME name on current_language.
|
| + const IBusEngineDesc* engine_desc
|
| + = ibus_input_context_get_engine(context);
|
| + DCHECK(engine_desc);
|
| + if (!engine_desc) {
|
| + return;
|
| + }
|
| + current_language = InputLanguage(LANGUAGE_CATEGORY_IME,
|
| + engine_desc->name,
|
| + engine_desc->longname,
|
| + engine_desc->icon);
|
| + } else {
|
| + DLOG(INFO) << "IME is not active";
|
| + // Set XKB layout name on current_languages.
|
| + current_language = InputLanguage(LANGUAGE_CATEGORY_XKB,
|
| + kFallbackXKBId,
|
| + kFallbackXKBDisplayName,
|
| + "" /* no icon */); // mock
|
| + // TODO(yusukes): implemente this.
|
| + }
|
| + DLOG(INFO) << "Updating the UI. ID:" << current_language.id
|
| + << ", display_name:" << current_language.display_name;
|
| +
|
| + // Notify the change to update UI.
|
| + monitor_function_(language_library_, current_language);
|
| + g_object_unref(context);
|
| + }
|
| +
|
| + // A function pointer which points LanguageLibrary::LanguageChangedHandler
|
| + // function. |monitor_funcion_| is called when Chrome UI needs to be updated.
|
| + LanguageStatusMonitorFunction monitor_function_;
|
| + // Points to a chromeos::LanguageLibrary object. |language_library_| is used
|
| + // as the first argument of the |monitor_function_| function.
|
| + void* language_library_;
|
| +
|
| + // Connections to IBus and DBus.
|
| + typedef dbus::MonitorConnection<void (const char*)>* DBusConnectionType;
|
| + IBusBus* ibus_;
|
| + DBusConnectionType dbus_focus_in_;
|
| + DBusConnectionType dbus_focus_out_;
|
| + DBusConnectionType dbus_state_changed_;
|
| +
|
| + // Current input context path.
|
| + std::string input_context_path_;
|
| +};
|
| +
|
| +//
|
| +// cros APIs
|
| +//
|
| +
|
| +// The function will be bound to chromeos::MonitorLanguageStatus with dlsym()
|
| +// in load.cc so it needs to be in the C linkage, so the symbol name does not
|
| +// get mangled.
|
| +extern "C"
|
| +LanguageStatusConnection* ChromeOSMonitorLanguageStatus(
|
| + LanguageStatusMonitorFunction monitor_function, void* language_library) {
|
| + LOG(INFO) << "MonitorLanguageStatus";
|
| + LanguageStatusConnection* connection
|
| + = new LanguageStatusConnection(monitor_function, language_library);
|
| + if (!connection->Init()) {
|
| + LOG(WARNING) << "Failed to Init() LanguageStatusConnection. "
|
| + << "Returning NULL";
|
| + delete connection;
|
| + connection = NULL;
|
| + }
|
| + return connection;
|
| +}
|
| +
|
| +extern "C"
|
| +void ChromeOSDisconnectLanguageStatus(LanguageStatusConnection* connection) {
|
| + LOG(INFO) << "DisconnectLanguageStatus";
|
| + delete connection;
|
| +}
|
| +
|
| +extern "C"
|
| +InputLanguageList* ChromeOSGetLanguages(LanguageStatusConnection* connection) {
|
| + // TODO(yusukes): Add DCHECK(connection); here when candidate_window for
|
| + // Chrome OS gets ready.
|
| + if (!connection) {
|
| + LOG(WARNING) << "LanguageStatusConnection is NULL";
|
| + return NULL;
|
| + }
|
| + // Pass ownership to a caller. Note: GetLanguages() might return NULL.
|
| + return connection->GetLanguages();
|
| +}
|
| +
|
| +extern "C"
|
| +void ChromeOSChangeLanguage(LanguageStatusConnection* connection,
|
| + LanguageCategory category,
|
| + const char* name) {
|
| + // TODO(yusukes): Add DCHECK(connection); here when candidate_window for
|
| + // Chrome OS gets ready.
|
| + if (!connection) {
|
| + LOG(WARNING) << "LanguageStatusConnection is NULL";
|
| + return;
|
| + }
|
| + DCHECK(name);
|
| + DLOG(INFO) << "ChangeLanguage: " << name << " [category " << category << "]";
|
| + switch (category) {
|
| + case LANGUAGE_CATEGORY_XKB:
|
| + connection->SwitchXKB(name);
|
| + break;
|
| + case LANGUAGE_CATEGORY_IME:
|
| + connection->SwitchIME(name);
|
| + break;
|
| + }
|
| +}
|
| +
|
| +} // namespace chromeos
|
|
|