Index: chromeos_language.cc |
diff --git a/chromeos_language.cc b/chromeos_language.cc |
index ef8fa104af02c1527d7ec232589c2180d9c212ec..58366875ba4b492353e607e823f0363f38e7ce3e 100644 |
--- a/chromeos_language.cc |
+++ b/chromeos_language.cc |
@@ -4,10 +4,13 @@ |
#include "chromeos_language.h" |
-#include <base/logging.h> |
#include <ibus.h> |
+#include <dbus/dbus-glib-lowlevel.h> // for dbus_g_connection_get_connection. |
#include <algorithm> // for std::sort. |
+#include <sstream> |
+#include <stack> |
+#include <utility> |
#include "chromeos/dbus/dbus.h" |
#include "chromeos/glib/object.h" |
@@ -18,6 +21,13 @@ const char kCandidateWindowService[] = "org.freedesktop.IBus.Panel"; |
const char kCandidateWindowObjectPath[] = "/org/chromium/Chrome/LanguageBar"; |
const char kCandidateWindowInterface[] = "org.freedesktop.IBus.Panel"; |
+// The list of IME property keys that we don't handle. |
+const char* kImePropertyKeysBlacklist[] = { |
+ "setup", // menu for showing setup dialog used in anthy and hangul. |
+ "chewing_settings_prop", // menu for showing setup dialog used in chewing. |
+ "status", // used in m17n. |
+}; |
+ |
// Copies IME names in |engines| to |out|. |
void AddIMELanguages(const GList* engines, chromeos::InputLanguageList* out) { |
DCHECK(out); |
@@ -41,6 +51,271 @@ void AddXKBLayouts(chromeos::InputLanguageList* out) { |
"" /* no icon */)); // mock |
} |
+// Returns IBusInputContext for |input_context_path|. NULL on errors. |
+IBusInputContext* GetInputContext( |
+ const std::string& input_context_path, IBusBus* ibus) { |
+ IBusInputContext* context = ibus_input_context_get_input_context( |
+ input_context_path.c_str(), ibus_bus_get_connection(ibus)); |
+ if (!context) { |
+ LOG(ERROR) << "IBusInputContext is null: " << input_context_path; |
+ } |
+ return context; |
+} |
+ |
+// Returns true if |key| is blacklisted. |
+bool KeyIsBlacklisted(const char* key) { |
+ for (size_t i = 0; i < arraysize(kImePropertyKeysBlacklist); ++i) { |
+ if (!std::strcmp(key, kImePropertyKeysBlacklist[i])) { |
+ return true; |
+ } |
+ } |
+ return false; |
+} |
+ |
+// Returns true if |prop| has children. |
+bool PropertyHasChildren(IBusProperty* prop) { |
+ return prop && prop->sub_props && ibus_prop_list_get(prop->sub_props, 0); |
+} |
+ |
+// This function is called by and FlattenProperty() and converts IBus |
+// representation of a property, |ibus_prop|, to our own and push_back the |
+// result to |out_prop_list|. This function returns true on success, and |
+// returns false if sanity checks for |ibus_prop| fail. |
+bool ConvertProperty(IBusProperty* ibus_prop, |
+ int selection_item_id, |
+ chromeos::ImePropertyList* out_prop_list) { |
+ DCHECK(ibus_prop); |
+ DCHECK(ibus_prop->key); |
+ DCHECK(out_prop_list); |
+ |
+ // Sanity checks. |
+ const bool has_sub_props = PropertyHasChildren(ibus_prop); |
+ if (has_sub_props && (ibus_prop->type != PROP_TYPE_MENU)) { |
+ LOG(ERROR) << "The property has sub properties, " |
+ << "but the type of the property is not PROP_TYPE_MENU"; |
+ return false; |
+ } |
+ if ((!has_sub_props) && (ibus_prop->type == PROP_TYPE_MENU)) { |
+ LOG(ERROR) << "The property does not have sub properties, " |
+ << "but the type of the property is PROP_TYPE_MENU"; |
+ return false; |
+ } |
+ if (ibus_prop->type == PROP_TYPE_SEPARATOR || |
+ ibus_prop->type == PROP_TYPE_MENU) { |
+ // This is not an error, but we don't push an item for these types. |
+ return true; |
+ } |
+ |
+ const bool is_selection_item = (ibus_prop->type == PROP_TYPE_RADIO); |
+ selection_item_id |
+ = is_selection_item ? selection_item_id : kInvalidSelectionItemId; |
+ |
+ bool is_selection_item_checked = false; |
+ if (ibus_prop->state == PROP_STATE_INCONSISTENT) { |
+ LOG(WARNING) << "The property is in PROP_STATE_INCONSISTENT, " |
+ << "which is not supported."; |
+ } else if ((!is_selection_item) && (ibus_prop->state == PROP_STATE_CHECKED)) { |
+ LOG(WARNING) << "PROP_STATE_CHECKED is meaningful only if the type is " |
+ << "PROP_TYPE_RADIO."; |
+ } else { |
+ is_selection_item_checked = (ibus_prop->state == PROP_STATE_CHECKED); |
+ } |
+ |
+ // TODO(yusukes): Probably it's better to generate our own label from the key? |
+ std::string label = (ibus_prop->tooltip ? ibus_prop->tooltip->text : ""); |
+ if (label.empty()) { |
+ // Usually tooltips are more descriptive than labels. |
+ label = (ibus_prop->label ? ibus_prop->label->text : ""); |
+ } |
+ if (label.empty()) { |
+ // ibus-pinyin has a property whose label and tooltip are empty. Fall back |
+ // to the key. |
+ label = ibus_prop->key; |
+ } |
+ |
+ out_prop_list->push_back(chromeos::ImeProperty(ibus_prop->key, |
+ ibus_prop->icon, |
+ label, |
+ is_selection_item, |
+ is_selection_item_checked, |
+ selection_item_id)); |
+ return true; |
+} |
+ |
+// Converts |ibus_prop| to |out_prop_list|. Please note that |ibus_prop| |
+// may or may not have children. See the comment for FlattenPropertyList |
+// for details. Returns true if no error is found. |
+bool FlattenProperty( |
+ IBusProperty* ibus_prop, chromeos::ImePropertyList* out_prop_list) { |
+ // TODO(yusukes): Find a good way to write unit tests for this function. |
+ DCHECK(ibus_prop); |
+ DCHECK(out_prop_list); |
+ |
+ int selection_item_id = -1; |
+ std::stack<std::pair<IBusProperty*, int> > prop_stack; |
+ prop_stack.push(std::make_pair(ibus_prop, selection_item_id)); |
+ |
+ while (!prop_stack.empty()) { |
+ IBusProperty* prop = prop_stack.top().first; |
+ const int current_selection_item_id = prop_stack.top().second; |
+ prop_stack.pop(); |
+ |
+ // Filter out unnecessary properties. |
+ if (KeyIsBlacklisted(prop->key)) { |
+ continue; |
+ } |
+ |
+ // Convert |prop| to ImeProperty and push it to |out_prop_list|. |
+ if (!ConvertProperty(prop, current_selection_item_id, out_prop_list)) { |
+ return false; |
+ } |
+ |
+ // Process childrens iteratively (if any). Push all sub properties to the |
+ // stack. |
+ if (PropertyHasChildren(prop)) { |
+ ++selection_item_id; |
+ for (int i = 0;; ++i) { |
+ IBusProperty* sub_prop = ibus_prop_list_get(prop->sub_props, i); |
+ if (!sub_prop) { |
+ break; |
+ } |
+ prop_stack.push(std::make_pair(sub_prop, selection_item_id)); |
+ } |
+ ++selection_item_id; |
+ } |
+ } |
+ std::reverse(out_prop_list->begin(), out_prop_list->end()); |
+ |
+ return true; |
+} |
+ |
+// Converts IBus representation of a property list, |ibus_prop_list| to our |
+// own. This function also flatten the original list (actually it's a tree). |
+// Returns true if no error is found. The conversion to our own type is |
+// necessary since our language switcher in Chrome tree don't (or can't) know |
+// IBus types. Here is an example: |
+// |
+// ====================================================================== |
+// Input: |
+// |
+// --- Item-1 |
+// |- Item-2 |
+// |- SubMenuRoot --- Item-3-1 |
+// | |- Item-3-2 |
+// | |- Item-3-3 |
+// |- Item-4 |
+// |
+// (Note: Item-3-X is a selection item since they're on a sub menu.) |
+// |
+// Output: |
+// |
+// Item-1, Item-2, Item-3-1, Item-3-2, Item-3-3, Item-4 |
+// (Note: SubMenuRoot does not appear in the output.) |
+// ====================================================================== |
+bool FlattenPropertyList( |
+ IBusPropList* ibus_prop_list, chromeos::ImePropertyList* out_prop_list) { |
+ // TODO(yusukes): Find a good way to write unit tests for this function. |
+ DCHECK(ibus_prop_list); |
+ DCHECK(out_prop_list); |
+ |
+ IBusProperty* fake_root_prop = ibus_property_new("Dummy.Key", |
+ PROP_TYPE_MENU, |
+ NULL, /* label */ |
+ "", /* icon */ |
+ NULL, /* tooltip */ |
+ FALSE, /* sensitive */ |
+ FALSE, /* visible */ |
+ PROP_STATE_UNCHECKED, |
+ ibus_prop_list); |
+ |
+ const bool result |
+ = fake_root_prop && FlattenProperty(fake_root_prop, out_prop_list); |
+ g_object_unref(fake_root_prop); |
+ |
+ return result; |
+} |
+ |
+// Debug print functions. |
+const char* PropTypeToString(int prop_type) { |
+ switch (static_cast<IBusPropType>(prop_type)) { |
+ case PROP_TYPE_NORMAL: |
+ return "NORMAL"; |
+ case PROP_TYPE_TOGGLE: |
+ return "TOGGLE"; |
+ case PROP_TYPE_RADIO: |
+ return "RADIO"; |
+ case PROP_TYPE_MENU: |
+ return "MENU"; |
+ case PROP_TYPE_SEPARATOR: |
+ return "SEPARATOR"; |
+ } |
+ return "UNKNOWN"; |
+} |
+ |
+const char* PropStateToString(int prop_state) { |
+ switch (static_cast<IBusPropState>(prop_state)) { |
+ case PROP_STATE_UNCHECKED: |
+ return "UNCHECKED"; |
+ case PROP_STATE_CHECKED: |
+ return "CHECKED"; |
+ case PROP_STATE_INCONSISTENT: |
+ return "INCONSISTENT"; |
+ } |
+ return "UNKNOWN"; |
+} |
+ |
+std::string Spacer(int n) { |
+ return std::string(n, ' '); |
+} |
+ |
+std::string PrintPropList(IBusPropList *prop_list, int tree_level); |
+std::string PrintProp(IBusProperty *prop, int tree_level) { |
+ if (!prop) { |
+ return ""; |
+ } |
+ |
+ std::stringstream stream; |
+ stream << Spacer(tree_level) << "=========================" << std::endl; |
+ stream << Spacer(tree_level) << "key: " << (prop->key ? prop->key : "<none>") |
+ << std::endl; |
+ stream << Spacer(tree_level) << "icon: " |
+ << (prop->icon ? prop->icon : "<none>") << std::endl; |
+ stream << Spacer(tree_level) << "label: " |
+ << (prop->label ? prop->label->text : "<none>") << std::endl; |
+ stream << Spacer(tree_level) << "tooptip: " |
+ << (prop->tooltip ? prop->tooltip->text : "<none>") << std::endl; |
+ stream << Spacer(tree_level) << "sensitive: " |
+ << (prop->sensitive ? "YES" : "NO") << std::endl; |
+ stream << Spacer(tree_level) << "visible: " << (prop->visible ? "YES" : "NO") |
+ << std::endl; |
+ stream << Spacer(tree_level) << "type: " << PropTypeToString(prop->type) |
+ << std::endl; |
+ stream << Spacer(tree_level) << "state: " << PropStateToString(prop->state) |
+ << std::endl; |
+ stream << Spacer(tree_level) << "sub_props: " |
+ << (PropertyHasChildren(prop) ? "" : "<none>") << std::endl; |
+ stream << PrintPropList(prop->sub_props, tree_level + 1); |
+ stream << Spacer(tree_level) << "=========================" << std::endl; |
+ |
+ return stream.str(); |
+} |
+ |
+std::string PrintPropList(IBusPropList *prop_list, int tree_level) { |
+ if (!prop_list) { |
+ return ""; |
+ } |
+ |
+ std::stringstream stream; |
+ for (int i = 0;; ++i) { |
+ IBusProperty* prop = ibus_prop_list_get(prop_list, i); |
+ if (!prop) { |
+ break; |
+ } |
+ stream << PrintProp(prop, tree_level); |
+ } |
+ return stream.str(); |
+} |
+ |
} // namespace |
namespace chromeos { |
@@ -48,17 +323,16 @@ namespace chromeos { |
// A class that holds IBus and DBus connections. |
class LanguageStatusConnection { |
public: |
- LanguageStatusConnection(LanguageStatusMonitorFunction monitor_function, |
+ LanguageStatusConnection(LanguageStatusMonitorFunctions monitor_functions, |
void* language_library) |
- : monitor_function_(monitor_function), |
+ : monitor_functions_(monitor_functions), |
language_library_(language_library), |
ibus_(NULL), |
ibus_config_(NULL), |
- dbus_focus_in_(NULL), |
- dbus_focus_out_(NULL), |
- dbus_state_changed_(NULL), |
input_context_path_("") { |
- DCHECK(monitor_function_); |
+ DCHECK(monitor_functions_.current_language); |
+ DCHECK(monitor_functions_.register_ime_properties); |
+ DCHECK(monitor_functions_.update_ime_property); |
DCHECK(language_library_); |
} |
@@ -70,15 +344,6 @@ class LanguageStatusConnection { |
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. |
@@ -112,43 +377,33 @@ class LanguageStatusConnection { |
return false; |
} |
- |
// Establish a DBus connection between the candidate_window process for |
- // Chromium OS to handle "FocusIn", "FocusOut", and "StateChanged" signals |
- // from the process. |
+ // Chromium OS to handle signals (e.g. "FocusIn") from the process. |
const char* address = ibus_get_address(); |
- dbus::BusConnection dbus(dbus::GetPrivateBusConnection(address)); |
+ dbus_connection_.reset( |
+ new dbus::BusConnection(dbus::GetPrivateBusConnection(address))); |
LOG(INFO) << "Established private D-Bus connection to: '" << address << "'"; |
+ // Connect to the candidate_window. Note that dbus_connection_add_filter() |
+ // does not work without calling dbus::Proxy(). |
+ // TODO(yusukes): Investigate how we can eliminate the call. |
+ |
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); |
+ dbus_proxy_.reset(new dbus::Proxy(*dbus_connection_, |
+ kCandidateWindowService, |
+ kCandidateWindowObjectPath, |
+ kCandidateWindowInterface, |
+ kConnectToNameOwner)); |
+ |
+ // Register DBus signal handler. |
+ dbus_connection_add_filter( |
+ dbus_g_connection_get_connection(dbus_connection_->g_connection()), |
+ &LanguageStatusConnection::DispatchSignalFromCandidateWindow, |
+ this, NULL); |
// TODO(yusukes): Investigate what happens if IBus/DBus connections are |
// suddenly closed. |
@@ -191,36 +446,46 @@ class LanguageStatusConnection { |
return language_list; |
} |
- // Called by cros API ChromeOSChangeLanguage(). |
- void SwitchXKB(const char* name) { |
- // TODO(yusukes): implement XKB switching. |
- |
+ // Called by cros API ChromeOS(Activate|Deactive)ImeProperty(). |
+ void ActivateOrDeactiveImeProperty(const char* key, bool active) { |
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_disable(context); |
+ IBusInputContext* context = GetInputContext(input_context_path_, ibus_); |
+ if (!context) { |
+ return; |
+ } |
+ ibus_input_context_property_activate( |
+ context, key, (active ? PROP_STATE_CHECKED : PROP_STATE_UNCHECKED)); |
g_object_unref(context); |
+ |
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; |
+ // Called by cros API ChromeOSChangeLanguage(). |
+ void ChangeLanguage(LanguageCategory category, const char* name) { |
+ // Clear all IME properties unconditionally. |
+ // - When switching to XKB, it's necessary since XKB layout does not have |
+ // IME properties. |
+ // - When switching to IME and a text area is focused, it's okay to clear |
+ // IME properties here since RegisterProperties signal for the new IME |
+ // will be sent anyway. |
+ // - When switching to IME and no text area is focused, RegisterProperties |
+ // signal for the new IME will NOT be sent until a text area is focused. |
+ // Therefore, we have to clear the old IME properties here to keep the |
+ // IME switcher status consistent. |
+ RegisterProperties(NULL); |
+ |
+ switch (category) { |
+ case LANGUAGE_CATEGORY_XKB: |
+ SwitchToXKB(name); |
+ break; |
+ case LANGUAGE_CATEGORY_IME: |
+ SwitchToIME(name); |
+ break; |
} |
- |
- 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(); |
} |
// UpdateMode is used for specifying whether we are activating or |
@@ -287,9 +552,10 @@ class LanguageStatusConnection { |
} |
InputLanguage* GetCurrentLanguage() { |
- IBusInputContext* context |
- = ibus_input_context_get_input_context( |
- input_context_path_.c_str(), ibus_bus_get_connection(ibus_)); |
+ IBusInputContext* context = GetInputContext(input_context_path_, ibus_); |
+ if (!context) { |
+ return NULL; |
+ } |
const bool ime_is_enabled = ibus_input_context_is_enabled(context); |
g_object_unref(context); |
@@ -321,35 +587,97 @@ class LanguageStatusConnection { |
} |
private: |
- // Handles "FocusIn" signal from candidate_window. |
- static void FocusIn(void* object, const char* input_context_path) { |
+ // Changes the current language to |name|, which is XKB layout. |
+ void SwitchToXKB(const char* name) { |
+ // TODO(yusukes): implement XKB switching. |
+ if (input_context_path_.empty()) { |
+ LOG(ERROR) << "Input context is unknown"; |
+ return; |
+ } |
+ |
+ IBusInputContext* context = GetInputContext(input_context_path_, ibus_); |
+ if (!context) { |
+ return; |
+ } |
+ ibus_input_context_disable(context); |
+ g_object_unref(context); |
+ UpdateUI(); |
+ } |
+ |
+ // Changes the current language to |name|, which is IME. |
+ void SwitchToIME(const char* name) { |
+ if (input_context_path_.empty()) { |
+ LOG(ERROR) << "Input context is unknown"; |
+ return; |
+ } |
+ |
+ IBusInputContext* context = GetInputContext(input_context_path_, ibus_); |
+ if (!context) { |
+ return; |
+ } |
+ ibus_input_context_set_engine(context, name); |
+ g_object_unref(context); |
+ UpdateUI(); |
+ } |
+ |
+ // Handles "FocusIn" signal from the candidate_window process. |
+ void FocusIn(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. |
+ input_context_path_ = input_context_path; |
+ 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) { |
+ // Handles "FocusOut" signal from the candidate_window process. |
+ void FocusOut(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) { |
+ // Handles "StateChanged" signal from the candidate_window process. |
+ void StateChanged() { |
// 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(); |
+ UpdateUI(); |
+ } |
+ |
+ // Handles "RegisterProperties" signal from the candidate_window process. |
+ void RegisterProperties(IBusPropList* ibus_prop_list) { |
+ DLOG(INFO) << "RegisterProperties" << (ibus_prop_list ? "" : " (clear)"); |
+ |
+ ImePropertyList prop_list; // our representation. |
+ if (ibus_prop_list) { |
+ // You can call |
+ // LOG(INFO) << "\n" << PrintPropList(ibus_prop_list, 0); |
+ // here to dump |ibus_prop_list|. |
+ if (!FlattenPropertyList(ibus_prop_list, &prop_list)) { |
+ LOG(WARNING) << "Malformed properties are detected"; |
+ } |
+ } |
+ // Notify the change. |
+ monitor_functions_.register_ime_properties(language_library_, prop_list); |
+ } |
+ |
+ // Handles "UpdateProperty" signal from the candidate_window process. |
+ void UpdateProperty(IBusProperty* ibus_prop) { |
+ DLOG(INFO) << "UpdateProperty"; |
+ DCHECK(ibus_prop); |
+ |
+ // You can call |
+ // LOG(INFO) << "\n" << PrintProp(ibus_prop, 0); |
+ // here to dump |ibus_prop|. |
+ |
+ ImePropertyList prop_list; // our representation. |
+ if (!FlattenProperty(ibus_prop, &prop_list)) { |
+ LOG(WARNING) << "Malformed properties are detected"; |
+ } |
+ // Notify the change. |
+ if (!prop_list.empty()) { |
+ monitor_functions_.update_ime_property(language_library_, prop_list); |
+ } |
} |
// Retrieve IME/XKB status and notify them to the UI. |
@@ -359,9 +687,10 @@ class LanguageStatusConnection { |
return; |
} |
- IBusInputContext* context |
- = ibus_input_context_get_input_context( |
- input_context_path_.c_str(), ibus_bus_get_connection(ibus_)); |
+ IBusInputContext* context = GetInputContext(input_context_path_, ibus_); |
+ if (!context) { |
+ return; |
+ } |
InputLanguage current_language; |
const bool ime_is_enabled = ibus_input_context_is_enabled(context); |
@@ -391,24 +720,125 @@ class LanguageStatusConnection { |
<< ", display_name:" << current_language.display_name; |
// Notify the change to update UI. |
- monitor_function_(language_library_, current_language); |
+ monitor_functions_.current_language(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_; |
+ // Dispatches signals from candidate_window. In this function, we use the |
+ // IBus's DBus binding (rather than the dbus-glib and its C++ wrapper). |
+ // This is because arguments of "RegisterProperties" and "UpdateProperty" |
+ // are fairly complex IBus types, and thus it's probably not a good idea |
+ // to write a deserializer for these types from scratch using dbus-glib. |
+ static DBusHandlerResult DispatchSignalFromCandidateWindow( |
+ DBusConnection* connection, DBusMessage* message, void* object) { |
+ DCHECK(message); |
+ DCHECK(object); |
+ |
+ LanguageStatusConnection* self |
+ = static_cast<LanguageStatusConnection*>(object); |
+ IBusError* error = NULL; |
+ |
+ if (ibus_message_is_signal(message, |
+ kCandidateWindowInterface, |
+ "FocusIn")) { |
+ gchar* input_context_path = NULL; |
+ const gboolean retval = ibus_message_get_args(message, &error, |
+ G_TYPE_STRING, |
+ &input_context_path, |
+ G_TYPE_INVALID); |
+ if (!retval) { |
+ LOG(ERROR) << "FocusIn"; |
+ return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; |
+ } |
+ self->FocusIn(input_context_path); |
+ return DBUS_HANDLER_RESULT_HANDLED; |
+ } |
+ |
+ if (ibus_message_is_signal(message, |
+ kCandidateWindowInterface, |
+ "FocusOut")) { |
+ gchar* input_context_path = NULL; |
+ const gboolean retval = ibus_message_get_args(message, &error, |
+ G_TYPE_STRING, |
+ &input_context_path, |
+ G_TYPE_INVALID); |
+ if (!retval) { |
+ LOG(ERROR) << "FocusOut"; |
+ return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; |
+ } |
+ self->FocusOut(input_context_path); |
+ return DBUS_HANDLER_RESULT_HANDLED; |
+ } |
+ |
+ if (ibus_message_is_signal(message, |
+ kCandidateWindowInterface, |
+ "StateChanged")) { |
+ const gboolean retval = ibus_message_get_args(message, &error, |
+ /* no arguments */ |
+ G_TYPE_INVALID); |
+ if (!retval) { |
+ LOG(ERROR) << "StateChanged"; |
+ return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; |
+ } |
+ self->StateChanged(); |
+ return DBUS_HANDLER_RESULT_HANDLED; |
+ } |
+ |
+ if (ibus_message_is_signal(message, |
+ kCandidateWindowInterface, |
+ "RegisterProperties")) { |
+ IBusPropList* prop_list = NULL; |
+ |
+ // The ibus_message_get_args() function automagically deserializes the |
+ // complex IBus structure. |
+ const gboolean retval = ibus_message_get_args(message, &error, |
+ IBUS_TYPE_PROP_LIST, |
+ &prop_list, |
+ G_TYPE_INVALID); |
+ |
+ if (!retval) { |
+ LOG(ERROR) << "RegisterProperties"; |
+ return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; |
+ } |
+ self->RegisterProperties(prop_list); |
+ g_object_unref(prop_list); |
+ return DBUS_HANDLER_RESULT_HANDLED; |
+ } |
+ |
+ if (ibus_message_is_signal(message, |
+ kCandidateWindowInterface, |
+ "UpdateProperty")) { |
+ IBusProperty* prop = NULL; |
+ const gboolean retval = ibus_message_get_args(message, &error, |
+ IBUS_TYPE_PROPERTY, |
+ &prop, |
+ G_TYPE_INVALID); |
+ if (!retval) { |
+ LOG(ERROR) << "UpdateProperty"; |
+ return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; |
+ } |
+ self->UpdateProperty(prop); |
+ g_object_unref(prop); |
+ return DBUS_HANDLER_RESULT_HANDLED; |
+ } |
+ |
+ return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; |
+ } |
+ |
+ // A function pointers which point LanguageLibrary::XXXHandler functions. |
+ // |monitor_funcions_| is used when libcros receives signals from the |
+ // candidate_window. |
+ LanguageStatusMonitorFunctions monitor_functions_; |
+ |
// Points to a chromeos::LanguageLibrary object. |language_library_| is used |
- // as the first argument of the |monitor_function_| function. |
+ // as the first argument of the |monitor_functions_| functions. |
void* language_library_; |
// Connections to IBus and DBus. |
- typedef dbus::MonitorConnection<void (const char*)>* DBusConnectionType; |
IBusBus* ibus_; |
IBusConfig* ibus_config_; |
- DBusConnectionType dbus_focus_in_; |
- DBusConnectionType dbus_focus_out_; |
- DBusConnectionType dbus_state_changed_; |
+ scoped_ptr<dbus::BusConnection> dbus_connection_; |
+ scoped_ptr<dbus::Proxy> dbus_proxy_; |
// Current input context path. |
std::string input_context_path_; |
@@ -423,10 +853,10 @@ class LanguageStatusConnection { |
// get mangled. |
extern "C" |
LanguageStatusConnection* ChromeOSMonitorLanguageStatus( |
- LanguageStatusMonitorFunction monitor_function, void* language_library) { |
+ LanguageStatusMonitorFunctions monitor_functions, void* language_library) { |
LOG(INFO) << "MonitorLanguageStatus"; |
LanguageStatusConnection* connection |
- = new LanguageStatusConnection(monitor_function, language_library); |
+ = new LanguageStatusConnection(monitor_functions, language_library); |
if (!connection->Init()) { |
LOG(WARNING) << "Failed to Init() LanguageStatusConnection. " |
<< "Returning NULL"; |
@@ -468,25 +898,46 @@ InputLanguageList* ChromeOSGetSupportedLanguages(LanguageStatusConnection* |
} |
extern "C" |
+void ChromeOSActivateImeProperty( |
+ LanguageStatusConnection* connection, const char* key) { |
+ DLOG(INFO) << "ActivateImeProperty"; |
+ DCHECK(key); |
+ // TODO(yusukes): Add DCHECK(connection); here when candidate_window for |
+ // Chrome OS gets ready. |
+ if (!connection) { |
+ LOG(WARNING) << "LanguageStatusConnection is NULL"; |
+ return; |
+ } |
+ connection->ActivateOrDeactiveImeProperty(key, true); |
+} |
+ |
+extern "C" |
+void ChromeOSDeactivateImeProperty( |
+ LanguageStatusConnection* connection, const char* key) { |
+ DLOG(INFO) << "DeactivateImeProperty"; |
+ DCHECK(key); |
+ // TODO(yusukes): Add DCHECK(connection); here when candidate_window for |
+ // Chrome OS gets ready. |
+ if (!connection) { |
+ LOG(WARNING) << "LanguageStatusConnection is NULL"; |
+ return; |
+ } |
+ connection->ActivateOrDeactiveImeProperty(key, false); |
+} |
+ |
+extern "C" |
void ChromeOSChangeLanguage(LanguageStatusConnection* connection, |
LanguageCategory category, |
const char* name) { |
+ DCHECK(name); |
+ DLOG(INFO) << "ChangeLanguage: " << 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; |
- } |
+ connection->ChangeLanguage(category, name); |
} |
// Helper function for ChromeOSActivateLanguage() and |