Index: chrome/browser/chromeos/input_method/ibus_controller.cc |
diff --git a/chrome/browser/chromeos/input_method/ibus_controller.cc b/chrome/browser/chromeos/input_method/ibus_controller.cc |
index da473785bb6c14dcb3816514ab26ac63b42ecb13..7307512b992bee5b81c5a59aecf3488cffae44f9 100644 |
--- a/chrome/browser/chromeos/input_method/ibus_controller.cc |
+++ b/chrome/browser/chromeos/input_method/ibus_controller.cc |
@@ -5,1176 +5,25 @@ |
#include "chrome/browser/chromeos/input_method/ibus_controller.h" |
#if defined(HAVE_IBUS) |
-#include <ibus.h> |
+#include "chrome/browser/chromeos/input_method/ibus_controller_impl.h" |
+#else |
+#include "chrome/browser/chromeos/input_method/mock_ibus_controller.h" |
#endif |
-#include <algorithm> // for std::reverse. |
-#include <cstdio> |
-#include <cstring> // for std::strcmp. |
-#include <set> |
-#include <sstream> |
-#include <stack> |
-#include <utility> |
- |
-#include "base/memory/scoped_ptr.h" |
-#include "base/observer_list.h" |
-#include "chrome/browser/chromeos/input_method/input_method_engine.h" |
-#include "chrome/browser/chromeos/input_method/input_method_manager.h" |
-#include "chrome/browser/chromeos/input_method/input_method_util.h" |
-#include "chrome/browser/chromeos/input_method/input_method_whitelist.h" |
-#include "chrome/browser/chromeos/input_method/input_methods.h" |
- |
namespace chromeos { |
namespace input_method { |
-#if defined(HAVE_IBUS) |
-const char kPanelObjectKey[] = "panel-object"; |
- |
-namespace { |
- |
-// Also defined in chrome/browser/chromeos/language_preferences.h (Chrome tree). |
-const char kGeneralSectionName[] = "general"; |
-const char kPreloadEnginesConfigName[] = "preload_engines"; |
- |
-// The list of input method property keys that we don't handle. |
-const char* kInputMethodPropertyKeysBlacklist[] = { |
- "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. |
-}; |
- |
-const char* Or(const char* str1, const char* str2) { |
- return str1 ? str1 : str2; |
-} |
- |
-// Returns true if |key| is blacklisted. |
-bool PropertyKeyIsBlacklisted(const char* key) { |
- for (size_t i = 0; i < arraysize(kInputMethodPropertyKeysBlacklist); ++i) { |
- if (!std::strcmp(key, kInputMethodPropertyKeysBlacklist[i])) { |
- return true; |
- } |
- } |
- return false; |
-} |
- |
-// Returns IBusInputContext for |input_context_path|. NULL on errors. |
-IBusInputContext* GetInputContext( |
- const std::string& input_context_path, IBusBus* ibus) { |
- GDBusConnection* connection = ibus_bus_get_connection(ibus); |
- if (!connection) { |
- LOG(ERROR) << "IBusConnection is null"; |
- return NULL; |
- } |
- // This function does not issue an IBus IPC. |
- IBusInputContext* context = ibus_input_context_get_input_context( |
- input_context_path.c_str(), connection); |
- if (!context) { |
- LOG(ERROR) << "IBusInputContext is null: " << input_context_path; |
- } |
- return context; |
-} |
- |
-// Returns true if |prop| has children. |
-bool PropertyHasChildren(IBusProperty* prop) { |
- return prop && ibus_property_get_sub_props(prop) && |
- ibus_prop_list_get( |
- // TODO(yusukes): Remove the cast when we migrate to ibus-1.5. |
- const_cast<IBusPropList*>(ibus_property_get_sub_props(prop)), 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, |
- InputMethodPropertyList* out_prop_list) { |
- DCHECK(ibus_prop); |
- DCHECK(out_prop_list); |
- |
- const IBusPropType type = ibus_property_get_prop_type(ibus_prop); |
- const IBusPropState state = ibus_property_get_state(ibus_prop); |
- const IBusText* tooltip = ibus_property_get_tooltip(ibus_prop); |
- const IBusText* label = ibus_property_get_label(ibus_prop); |
- const gchar* key = ibus_property_get_key(ibus_prop); |
- DCHECK(key); |
- |
- // Sanity checks. |
- const bool has_sub_props = PropertyHasChildren(ibus_prop); |
- if (has_sub_props && (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) && (type == PROP_TYPE_MENU)) { |
- // This is usually not an error. ibus-daemon sometimes sends empty props. |
- VLOG(1) << "Property list is empty"; |
- return false; |
- } |
- if (type == PROP_TYPE_SEPARATOR || 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 = (type == PROP_TYPE_RADIO); |
- selection_item_id = is_selection_item ? |
- selection_item_id : InputMethodProperty::kInvalidSelectionItemId; |
- |
- bool is_selection_item_checked = false; |
- if (state == PROP_STATE_INCONSISTENT) { |
- LOG(WARNING) << "The property is in PROP_STATE_INCONSISTENT, " |
- << "which is not supported."; |
- } else if ((!is_selection_item) && (state == PROP_STATE_CHECKED)) { |
- LOG(WARNING) << "PROP_STATE_CHECKED is meaningful only if the type is " |
- << "PROP_TYPE_RADIO."; |
- } else { |
- is_selection_item_checked = (state == PROP_STATE_CHECKED); |
- } |
- |
- if (!key) { |
- LOG(ERROR) << "key is NULL"; |
- } |
- if (tooltip && !tooltip->text) { |
- LOG(ERROR) << "tooltip is NOT NULL, but tooltip->text IS NULL: key=" |
- << Or(key, ""); |
- } |
- if (label && !label->text) { |
- LOG(ERROR) << "label is NOT NULL, but label->text IS NULL: key=" |
- << Or(key, ""); |
- } |
- |
- // This label will be localized on Chrome side. |
- // See src/chrome/browser/chromeos/status/language_menu_l10n_util.h. |
- std::string label_to_use = ((tooltip && tooltip->text) ? tooltip->text : ""); |
- if (label_to_use.empty()) { |
- // Usually tooltips are more descriptive than labels. |
- label_to_use = (label && label->text) ? label->text : ""; |
- } |
- if (label_to_use.empty()) { |
- // ibus-pinyin has a property whose label and tooltip are empty. Fall back |
- // to the key. |
- label_to_use = Or(key, ""); |
- } |
- |
- out_prop_list->push_back(InputMethodProperty(key, |
- label_to_use, |
- 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. |
-// TODO(yusukes): Write unittest. |
-bool FlattenProperty(IBusProperty* ibus_prop, |
- InputMethodPropertyList* out_prop_list) { |
- 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 gchar* key = ibus_property_get_key(prop); |
- const int current_selection_item_id = prop_stack.top().second; |
- prop_stack.pop(); |
- |
- // Filter out unnecessary properties. |
- if (PropertyKeyIsBlacklisted(key)) { |
- continue; |
- } |
- |
- // Convert |prop| to InputMethodProperty 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( |
- // TODO(yusukes): Remove the cast when we migrate to ibus-1.5. |
- const_cast<IBusPropList*>(ibus_property_get_sub_props(prop)), 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.) |
-// ====================================================================== |
-// TODO(yusukes): Write unittest. |
-bool FlattenPropertyList( |
- IBusPropList* ibus_prop_list, InputMethodPropertyList* out_prop_list) { |
- 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); |
- g_return_val_if_fail(fake_root_prop, false); |
- // Increase the ref count so it won't get deleted when |fake_root_prop| |
- // is deleted. |
- g_object_ref(ibus_prop_list); |
- const bool result = FlattenProperty(fake_root_prop, out_prop_list); |
- g_object_unref(fake_root_prop); |
- |
- return result; |
-} |
- |
-// Debug print function. |
-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"; |
-} |
- |
-// Debug print function. |
-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"; |
-} |
- |
-// Debug print function. |
-std::string Spacer(int n) { |
- return std::string(n, ' '); |
-} |
- |
-std::string PrintPropList(IBusPropList *prop_list, int tree_level); |
-// Debug print function. |
-std::string PrintProp(IBusProperty *prop, int tree_level) { |
- if (!prop) { |
- return ""; |
- } |
- |
- const IBusPropType type = ibus_property_get_prop_type(prop); |
- const IBusPropState state = ibus_property_get_state(prop); |
- const IBusText* tooltip = ibus_property_get_tooltip(prop); |
- const IBusText* label = ibus_property_get_label(prop); |
- const gchar* key = ibus_property_get_key(prop); |
- |
- std::stringstream stream; |
- stream << Spacer(tree_level) << "=========================" << std::endl; |
- stream << Spacer(tree_level) << "key: " << Or(key, "<none>") |
- << std::endl; |
- stream << Spacer(tree_level) << "label: " |
- << ((label && label->text) ? label->text : "<none>") |
- << std::endl; |
- stream << Spacer(tree_level) << "tooptip: " |
- << ((tooltip && tooltip->text) |
- ? tooltip->text : "<none>") << std::endl; |
- stream << Spacer(tree_level) << "sensitive: " |
- << (ibus_property_get_sensitive(prop) ? "YES" : "NO") << std::endl; |
- stream << Spacer(tree_level) << "visible: " |
- << (ibus_property_get_visible(prop) ? "YES" : "NO") << std::endl; |
- stream << Spacer(tree_level) << "type: " << PropTypeToString(type) |
- << std::endl; |
- stream << Spacer(tree_level) << "state: " << PropStateToString(state) |
- << std::endl; |
- stream << Spacer(tree_level) << "sub_props: " |
- << (PropertyHasChildren(prop) ? "" : "<none>") << std::endl; |
- stream << PrintPropList( |
- // TODO(yusukes): Remove the cast when we migrate to ibus-1.5. |
- const_cast<IBusPropList*>(ibus_property_get_sub_props(prop)), |
- tree_level + 1); |
- stream << Spacer(tree_level) << "=========================" << std::endl; |
- |
- return stream.str(); |
-} |
- |
-// Debug print function. |
-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(); |
+IBusController::~IBusController() { |
} |
-} // namespace |
- |
-// The real implementation of the IBusController. |
-class IBusControllerImpl : public IBusController { |
- public: |
- IBusControllerImpl() |
- : ibus_(NULL), |
- ibus_config_(NULL) { |
- } |
- |
- ~IBusControllerImpl() { |
- // Disconnect signals so the handler functions will not be called with |
- // |this| which is already freed. |
- if (ibus_) { |
- g_signal_handlers_disconnect_by_func( |
- ibus_, |
- reinterpret_cast<gpointer>(G_CALLBACK(IBusBusConnectedThunk)), |
- this); |
- g_signal_handlers_disconnect_by_func( |
- ibus_, |
- reinterpret_cast<gpointer>(G_CALLBACK(IBusBusDisconnectedThunk)), |
- this); |
- g_signal_handlers_disconnect_by_func( |
- ibus_, |
- reinterpret_cast<gpointer>(G_CALLBACK(IBusBusNameOwnerChangedThunk)), |
- this); |
- |
- // Disconnect signals for the panel service as well. |
- IBusPanelService* ibus_panel_service = IBUS_PANEL_SERVICE( |
- g_object_get_data(G_OBJECT(ibus_), kPanelObjectKey)); |
- if (ibus_panel_service) { |
- g_signal_handlers_disconnect_by_func( |
- ibus_panel_service, |
- reinterpret_cast<gpointer>(G_CALLBACK(FocusInThunk)), |
- this); |
- g_signal_handlers_disconnect_by_func( |
- ibus_panel_service, |
- reinterpret_cast<gpointer>(G_CALLBACK(RegisterPropertiesThunk)), |
- this); |
- g_signal_handlers_disconnect_by_func( |
- ibus_panel_service, |
- reinterpret_cast<gpointer>(G_CALLBACK(UpdatePropertyThunk)), |
- this); |
- } |
- } |
- } |
- |
- virtual void Connect() { |
- MaybeRestoreConnections(); |
- } |
- |
- // IBusController override. |
- virtual bool StopInputMethodProcess() { |
- if (!IBusConnectionsAreAlive()) { |
- LOG(ERROR) << "StopInputMethodProcess: IBus connection is not alive"; |
- return false; |
- } |
- |
- // Ask IBus to exit *asynchronously*. |
- ibus_bus_exit_async(ibus_, |
- FALSE /* do not restart */, |
- -1 /* timeout */, |
- NULL /* cancellable */, |
- NULL /* callback */, |
- NULL /* user_data */); |
- |
- if (ibus_config_) { |
- // Release |ibus_config_| unconditionally to make sure next |
- // IBusConnectionsAreAlive() call will return false. |
- g_object_unref(ibus_config_); |
- ibus_config_ = NULL; |
- } |
- return true; |
- } |
- |
- // IBusController override. |
- virtual void SetImePropertyActivated(const std::string& key, |
- bool activated) { |
- if (!IBusConnectionsAreAlive()) { |
- LOG(ERROR) << "SetImePropertyActivated: IBus connection is not alive"; |
- return; |
- } |
- if (key.empty()) { |
- return; |
- } |
- if (input_context_path_.empty()) { |
- LOG(ERROR) << "Input context is unknown"; |
- return; |
- } |
- |
- IBusInputContext* context = GetInputContext(input_context_path_, ibus_); |
- if (!context) { |
- return; |
- } |
- // Activate the property *asynchronously*. |
- ibus_input_context_property_activate( |
- context, key.c_str(), |
- (activated ? PROP_STATE_CHECKED : PROP_STATE_UNCHECKED)); |
- |
- // We don't have to call ibus_proxy_destroy(context) explicitly here, |
- // i.e. we can just call g_object_unref(context), since g_object_unref can |
- // trigger both dispose, which is overridden by src/ibusproxy.c, and |
- // finalize functions. For details, see |
- // http://library.gnome.org/devel/gobject/stable/gobject-memory.html |
- g_object_unref(context); |
- } |
- |
- // IBusController override. |
- virtual bool ChangeInputMethod(const std::string& name) { |
- DCHECK(!InputMethodUtil::IsKeyboardLayout(name)); |
- |
- if (!IBusConnectionsAreAlive()) { |
- LOG(ERROR) << "ChangeInputMethod: IBus connection is not alive"; |
- return false; |
- } |
- if (name.empty()) { |
- return false; |
- } |
- if (!InputMethodIdIsWhitelisted(name) && |
- name.find(kExtensionImePrefix) != 0) { |
- return false; |
- } |
- |
- // Clear all input method properties unconditionally. |
- // |
- // When switching to another input method and no text area is focused, |
- // RegisterProperties signal for the new input method will NOT be sent |
- // until a text area is focused. Therefore, we have to clear the old input |
- // method properties here to keep the input method switcher status |
- // consistent. |
- DoRegisterProperties(NULL); |
- |
- // Change the global engine *asynchronously*. |
- ibus_bus_set_global_engine_async(ibus_, |
- name.c_str(), |
- -1, // use the default ibus timeout |
- NULL, // cancellable |
- NULL, // callback |
- NULL); // user_data |
- |
- UpdateUI(name.c_str()); |
- return true; |
- } |
- |
- // IBusController override. |
- virtual bool SetInputMethodConfig(const std::string& section, |
- const std::string& config_name, |
- const InputMethodConfigValue& value) { |
- // See comments in GetImeConfig() where ibus_config_get_value() is used. |
- if (!IBusConnectionsAreAlive()) { |
- LOG(ERROR) << "SetInputMethodConfig: IBus connection is not alive"; |
- return false; |
- } |
- |
- bool is_preload_engines = false; |
- |
- // Sanity check: do not preload unknown/unsupported input methods. |
- std::vector<std::string> string_list; |
- if ((value.type == InputMethodConfigValue::kValueTypeStringList) && |
- (section == kGeneralSectionName) && |
- (config_name == kPreloadEnginesConfigName)) { |
- FilterInputMethods(value.string_list_value, &string_list); |
- if (string_list.empty()) { |
- return true; |
- } |
- is_preload_engines = true; |
- } else { |
- string_list = value.string_list_value; |
- } |
- |
- // Convert the type of |value| from our structure to GVariant. |
- GVariant* variant = NULL; |
- switch (value.type) { |
- case InputMethodConfigValue::kValueTypeString: |
- variant = g_variant_new_string(value.string_value.c_str()); |
- break; |
- case InputMethodConfigValue::kValueTypeInt: |
- variant = g_variant_new_int32(value.int_value); |
- break; |
- case InputMethodConfigValue::kValueTypeBool: |
- variant = g_variant_new_boolean(value.bool_value); |
- break; |
- case InputMethodConfigValue::kValueTypeStringList: |
- GVariantBuilder variant_builder; |
- g_variant_builder_init(&variant_builder, G_VARIANT_TYPE("as")); |
- DCHECK(!string_list.empty()); |
- const size_t size = string_list.size(); // don't use string_list_value. |
- for (size_t i = 0; i < size; ++i) { |
- g_variant_builder_add(&variant_builder, "s", string_list[i].c_str()); |
- } |
- variant = g_variant_builder_end(&variant_builder); |
- break; |
- } |
- |
- if (!variant) { |
- LOG(ERROR) << "SetInputMethodConfig: variant is NULL"; |
- return false; |
- } |
- DCHECK(g_variant_is_floating(variant)); |
- |
- // Set an ibus configuration value *asynchronously*. |
- ibus_config_set_value_async(ibus_config_, |
- section.c_str(), |
- config_name.c_str(), |
- variant, |
- -1, // use the default ibus timeout |
- NULL, // cancellable |
- SetInputMethodConfigCallback, |
- g_object_ref(ibus_config_)); |
- |
- // Since |variant| is floating, ibus_config_set_value_async consumes |
- // (takes ownership of) the variable. |
- |
- if (is_preload_engines) { |
- VLOG(1) << "SetInputMethodConfig: " << section << "/" << config_name |
- << ": " << value.ToString(); |
- } |
- return true; |
- } |
- |
- // IBusController override. |
- virtual void SendHandwritingStroke(const HandwritingStroke& stroke) { |
- if (stroke.size() < 2) { |
- LOG(WARNING) << "Empty stroke data or a single dot is passed."; |
- return; |
- } |
- |
- IBusInputContext* context = GetInputContext(input_context_path_, ibus_); |
- if (!context) { |
- return; |
- } |
- |
- const size_t raw_stroke_size = stroke.size() * 2; |
- scoped_array<double> raw_stroke(new double[raw_stroke_size]); |
- for (size_t n = 0; n < stroke.size(); ++n) { |
- raw_stroke[n * 2] = stroke[n].first; // x |
- raw_stroke[n * 2 + 1] = stroke[n].second; // y |
- } |
- ibus_input_context_process_hand_writing_event( |
- context, raw_stroke.get(), raw_stroke_size); |
- g_object_unref(context); |
- } |
- |
- // IBusController override. |
- virtual void CancelHandwriting(int n_strokes) { |
- IBusInputContext* context = GetInputContext(input_context_path_, ibus_); |
- if (!context) { |
- return; |
- } |
- ibus_input_context_cancel_hand_writing(context, n_strokes); |
- g_object_unref(context); |
- } |
- |
- virtual bool InputMethodIdIsWhitelisted(const std::string& input_method_id) { |
- return whitelist_.InputMethodIdIsWhitelisted(input_method_id); |
- } |
- |
- virtual bool XkbLayoutIsSupported(const std::string& xkb_layout) { |
- return whitelist_.XkbLayoutIsSupported(xkb_layout); |
- } |
- |
- virtual InputMethodDescriptor CreateInputMethodDescriptor( |
- const std::string& id, |
- const std::string& name, |
- const std::string& raw_layout, |
- const std::string& language_code) { |
- return InputMethodDescriptor(whitelist_, id, name, raw_layout, |
- language_code); |
- } |
- |
- // IBusController override. |
- virtual void AddObserver(Observer* observer) { |
- observers_.AddObserver(observer); |
- } |
- |
- // IBusController override. |
- virtual void RemoveObserver(Observer* observer) { |
- observers_.RemoveObserver(observer); |
- } |
- |
- private: |
- // Functions that end with Thunk are used to deal with glib callbacks. |
- // |
- // Note that we cannot use CHROMEG_CALLBACK_0() here as we'll define |
- // IBusBusConnected() inline. If we are to define the function outside |
- // of the class definition, we should use CHROMEG_CALLBACK_0() here. |
- // |
- // CHROMEG_CALLBACK_0(Impl, |
- // void, IBusBusConnected, IBusBus*); |
- static void IBusBusConnectedThunk(IBusBus* sender, gpointer userdata) { |
- return reinterpret_cast<IBusControllerImpl*>(userdata) |
- ->IBusBusConnected(sender); |
- } |
- static void IBusBusDisconnectedThunk(IBusBus* sender, gpointer userdata) { |
- return reinterpret_cast<IBusControllerImpl*>(userdata) |
- ->IBusBusDisconnected(sender); |
- } |
- |
- static void IBusBusNameOwnerChangedThunk(IBusBus* sender, |
- const gchar* name, |
- const gchar* old_name, |
- const gchar* new_name, |
- gpointer userdata) { |
- return reinterpret_cast<IBusControllerImpl*>(userdata) |
- ->IBusBusNameOwnerChanged(sender, name, old_name, new_name); |
- } |
- static void FocusInThunk(IBusPanelService* sender, |
- const gchar* input_context_path, |
- gpointer userdata) { |
- return reinterpret_cast<IBusControllerImpl*>(userdata) |
- ->FocusIn(sender, input_context_path); |
- } |
- static void RegisterPropertiesThunk(IBusPanelService* sender, |
- IBusPropList* prop_list, |
- gpointer userdata) { |
- return reinterpret_cast<IBusControllerImpl*>(userdata) |
- ->RegisterProperties(sender, prop_list); |
- } |
- static void UpdatePropertyThunk(IBusPanelService* sender, |
- IBusProperty* ibus_prop, |
- gpointer userdata) { |
- return reinterpret_cast<IBusControllerImpl*>(userdata) |
- ->UpdateProperty(sender, ibus_prop); |
- } |
- |
- // Checks if |ibus_| and |ibus_config_| connections are alive. |
- bool IBusConnectionsAreAlive() { |
- return ibus_ && ibus_bus_is_connected(ibus_) && ibus_config_; |
- } |
- |
- // Restores connections to ibus-daemon and ibus-memconf if they are not ready. |
- // If both |ibus_| and |ibus_config_| become ready, the function sends a |
- // notification to Chrome. |
- void MaybeRestoreConnections() { |
- if (IBusConnectionsAreAlive()) { |
- return; |
- } |
- MaybeCreateIBus(); |
- MaybeRestoreIBusConfig(); |
- if (IBusConnectionsAreAlive()) { |
- ConnectPanelServiceSignals(); |
- VLOG(1) << "Notifying Chrome that IBus is ready."; |
- FOR_EACH_OBSERVER(Observer, observers_, OnConnectionChange(true)); |
- } |
- } |
- |
- // Creates IBusBus object if it's not created yet. |
- void MaybeCreateIBus() { |
- if (ibus_) { |
- return; |
- } |
- |
- ibus_init(); |
- // Establish IBus connection between ibus-daemon to retrieve the list of |
- // available input method engines, change the current input method engine, |
- // and so on. |
- ibus_ = ibus_bus_new(); |
- |
- // Check the IBus connection status. |
- if (!ibus_) { |
- LOG(ERROR) << "ibus_bus_new() failed"; |
- return; |
- } |
- // Register callback functions for IBusBus signals. |
- ConnectIBusSignals(); |
- |
- // Ask libibus to watch the NameOwnerChanged signal *asynchronously*. |
- ibus_bus_set_watch_dbus_signal(ibus_, TRUE); |
- |
- if (ibus_bus_is_connected(ibus_)) { |
- VLOG(1) << "IBus connection is ready."; |
- } |
- } |
- |
- // Creates IBusConfig object if it's not created yet AND |ibus_| connection |
- // is ready. |
- void MaybeRestoreIBusConfig() { |
- if (!ibus_) { |
- return; |
- } |
- |
- // Destroy the current |ibus_config_| object. No-op if it's NULL. |
- MaybeDestroyIBusConfig(); |
- |
- if (!ibus_config_) { |
- GDBusConnection* ibus_connection = ibus_bus_get_connection(ibus_); |
- if (!ibus_connection) { |
- VLOG(1) << "Couldn't create an ibus config object since " |
- << "IBus connection is not ready."; |
- return; |
- } |
- const gboolean disconnected |
- = g_dbus_connection_is_closed(ibus_connection); |
- if (disconnected) { |
- // |ibus_| object is not NULL, but the connection between ibus-daemon |
- // is not yet established. In this case, we don't create |ibus_config_| |
- // object. |
- LOG(ERROR) << "Couldn't create an ibus config object since " |
- << "IBus connection is closed."; |
- return; |
- } |
- // If memconf is not successfully started yet, ibus_config_new() will |
- // return NULL. Otherwise, it returns a transfer-none and non-floating |
- // object. ibus_config_new() sometimes issues a D-Bus *synchronous* IPC |
- // to check if the org.freedesktop.IBus.Config service is available. |
- ibus_config_ = ibus_config_new(ibus_connection, |
- NULL /* do not cancel the operation */, |
- NULL /* do not get error information */); |
- if (!ibus_config_) { |
- LOG(ERROR) << "ibus_config_new() failed. ibus-memconf is not ready?"; |
- return; |
- } |
- |
- // TODO(yusukes): g_object_weak_ref might be better since it allows |
- // libcros to detect the delivery of the "destroy" glib signal the |
- // |ibus_config_| object. |
- g_object_ref(ibus_config_); |
- VLOG(1) << "ibus_config_ is ready."; |
- } |
- } |
- |
- // Destroys IBusConfig object if |ibus_| connection is not ready. This |
- // function does nothing if |ibus_config_| is NULL or |ibus_| connection is |
- // still alive. Note that the IBusConfig object can't be used when |ibus_| |
- // connection is not ready. |
- void MaybeDestroyIBusConfig() { |
- if (!ibus_) { |
- LOG(ERROR) << "MaybeDestroyIBusConfig: ibus_ is NULL"; |
- return; |
- } |
- if (ibus_config_ && !ibus_bus_is_connected(ibus_)) { |
- g_object_unref(ibus_config_); |
- ibus_config_ = NULL; |
- } |
- } |
- |
- // Handles "RegisterProperties" signal from chromeos_input_method_ui. |
- void DoRegisterProperties(IBusPropList* ibus_prop_list) { |
- VLOG(1) << "RegisterProperties" << (ibus_prop_list ? "" : " (clear)"); |
- |
- InputMethodPropertyList 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)) { |
- // Clear properties on errors. |
- DoRegisterProperties(NULL); |
- return; |
- } |
- } |
- VLOG(1) << "RegisterProperties" << (ibus_prop_list ? "" : " (clear)"); |
- // Notify the change. |
- FOR_EACH_OBSERVER(Observer, observers_, |
- OnRegisterImeProperties(prop_list)); |
- } |
- |
- // Retrieves input method status and notifies them to the UI. |
- // |current_global_engine_id| is the current global engine id such as "mozc" |
- // and "xkb:us::eng". If the id is unknown, an empty string "" can be passed. |
- // Warning: you can call this function only from ibus callback functions |
- // like FocusIn(). See http://crosbug.com/5217#c9 for details. |
- void UpdateUI(const char* current_global_engine_id) { |
- DCHECK(current_global_engine_id); |
- |
- const InputMethodsInfo* engine_info = NULL; |
- for (size_t i = 0; i < arraysize(kInputMethods); ++i) { |
- if (kInputMethods[i].input_method_id == |
- std::string(current_global_engine_id)) { |
- engine_info = &kInputMethods[i]; |
- break; |
- } |
- } |
- |
- InputMethodDescriptor current_input_method; |
- if (engine_info) { |
- current_input_method = CreateInputMethodDescriptor( |
- engine_info->input_method_id, |
- "", |
- engine_info->xkb_layout_id, |
- engine_info->language_code); |
- } else { |
- if (!InputMethodManager::GetInstance()->GetExtraDescriptor( |
- current_global_engine_id, ¤t_input_method)) { |
- LOG(ERROR) << current_global_engine_id |
- << " is not found in the input method white-list."; |
- return; |
- } |
- } |
- |
- |
- VLOG(1) << "Updating the UI. ID:" << current_input_method.id() |
- << ", keyboard_layout:" << current_input_method.keyboard_layout(); |
- |
- // Notify the change to update UI. |
- FOR_EACH_OBSERVER(Observer, observers_, |
- OnCurrentInputMethodChanged(current_input_method)); |
- } |
- |
- // Installs gobject signal handlers to |ibus_|. |
- void ConnectIBusSignals() { |
- if (!ibus_) { |
- return; |
- } |
- |
- // We use g_signal_connect_after here since the callback should be called |
- // *after* the IBusBusDisconnectedCallback in chromeos_input_method_ui.cc |
- // is called. chromeos_input_method_ui.cc attaches the panel service object |
- // to |ibus_|, and the callback in this file use the attached object. |
- g_signal_connect_after(ibus_, |
- "connected", |
- G_CALLBACK(IBusBusConnectedThunk), |
- this); |
- |
- g_signal_connect(ibus_, |
- "disconnected", |
- G_CALLBACK(IBusBusDisconnectedThunk), |
- this); |
- |
- g_signal_connect(ibus_, |
- "name-owner-changed", |
- G_CALLBACK(IBusBusNameOwnerChangedThunk), |
- this); |
- } |
- |
- // Installs gobject signal handlers to the panel service. |
- void ConnectPanelServiceSignals() { |
- if (!ibus_) { |
- return; |
- } |
- |
- IBusPanelService* ibus_panel_service = IBUS_PANEL_SERVICE( |
- g_object_get_data(G_OBJECT(ibus_), kPanelObjectKey)); |
- if (!ibus_panel_service) { |
- LOG(ERROR) << "IBusPanelService is NOT available."; |
- return; |
- } |
- // We don't _ref() or _weak_ref() the panel service object, since we're not |
- // interested in the life time of the object. |
- |
- g_signal_connect(ibus_panel_service, |
- "focus-in", |
- G_CALLBACK(FocusInThunk), |
- this); |
- g_signal_connect(ibus_panel_service, |
- "register-properties", |
- G_CALLBACK(RegisterPropertiesThunk), |
- this); |
- g_signal_connect(ibus_panel_service, |
- "update-property", |
- G_CALLBACK(UpdatePropertyThunk), |
- this); |
- } |
- |
- // Handles "connected" signal from ibus-daemon. |
- void IBusBusConnected(IBusBus* bus) { |
- LOG(WARNING) << "IBus connection is recovered."; |
- MaybeRestoreConnections(); |
- } |
- |
- // Handles "disconnected" signal from ibus-daemon. |
- void IBusBusDisconnected(IBusBus* bus) { |
- LOG(WARNING) << "IBus connection is terminated."; |
- // ibus-daemon might be terminated. Since |ibus_| object will automatically |
- // connect to the daemon if it restarts, we don't have to set NULL on ibus_. |
- // Call MaybeDestroyIBusConfig() to set |ibus_config_| to NULL temporarily. |
- MaybeDestroyIBusConfig(); |
- VLOG(1) << "Notifying Chrome that IBus is terminated."; |
- FOR_EACH_OBSERVER(Observer, observers_, OnConnectionChange(false)); |
- } |
- |
- // Handles "name-owner-changed" signal from ibus-daemon. The signal is sent |
- // to libcros when an IBus component such as ibus-memconf, ibus-engine-*, .. |
- // is started. |
- void IBusBusNameOwnerChanged(IBusBus* bus, |
- const gchar* name, |
- const gchar* old_name, |
- const gchar* new_name) { |
- DCHECK(name); |
- DCHECK(old_name); |
- DCHECK(new_name); |
- VLOG(1) << "Name owner is changed: name=" << name |
- << ", old_name=" << old_name << ", new_name=" << new_name; |
- |
- if (name != std::string("org.freedesktop.IBus.Config")) { |
- // Not a signal for ibus-memconf. |
- return; |
- } |
- |
- const std::string empty_string; |
- if (old_name != empty_string || new_name == empty_string) { |
- // ibus-memconf died? |
- LOG(WARNING) << "Unexpected name owner change: name=" << name |
- << ", old_name=" << old_name << ", new_name=" << new_name; |
- // TODO(yusukes): it might be nice to set |ibus_config_| to NULL and call |
- // |OnConnectionChange| with false here to allow Chrome to |
- // recover all input method configurations when ibus-memconf is |
- // automatically restarted by ibus-daemon. Though ibus-memconf is pretty |
- // stable and unlikely crashes. |
- return; |
- } |
- |
- VLOG(1) << "IBus config daemon is started. Recovering ibus_config_"; |
- |
- // Try to recover |ibus_config_|. If the |ibus_config_| object is |
- // successfully created, |OnConnectionChange| will be called to |
- // notify Chrome that IBus is ready. |
- MaybeRestoreConnections(); |
- } |
- |
- // Handles "FocusIn" signal from chromeos_input_method_ui. |
- void FocusIn(IBusPanelService* panel, const gchar* input_context_path) { |
- if (!input_context_path) { |
- LOG(ERROR) << "NULL context passed"; |
- } else { |
- VLOG(1) << "FocusIn: " << input_context_path; |
- } |
- // Remember the current ic path. |
- input_context_path_ = Or(input_context_path, ""); |
- } |
- |
- // Handles "RegisterProperties" signal from chromeos_input_method_ui. |
- void RegisterProperties(IBusPanelService* panel, IBusPropList* prop_list) { |
- DoRegisterProperties(prop_list); |
- } |
- |
- // Handles "UpdateProperty" signal from chromeos_input_method_ui. |
- void UpdateProperty(IBusPanelService* panel, IBusProperty* ibus_prop) { |
- VLOG(1) << "UpdateProperty"; |
- DCHECK(ibus_prop); |
- |
- // You can call |
- // LOG(INFO) << "\n" << PrintProp(ibus_prop, 0); |
- // here to dump |ibus_prop|. |
- |
- InputMethodPropertyList prop_list; // our representation. |
- if (!FlattenProperty(ibus_prop, &prop_list)) { |
- // Don't update the UI on errors. |
- LOG(ERROR) << "Malformed properties are detected"; |
- return; |
- } |
- // Notify the change. |
- if (!prop_list.empty()) { |
- FOR_EACH_OBSERVER(Observer, observers_, |
- OnUpdateImeProperty(prop_list)); |
- } |
- } |
- |
- // Removes input methods that are not whitelisted from |
- // |requested_input_methods| and stores them on |out_filtered_input_methods|. |
- // TODO(yusukes): Write unittest. |
- void FilterInputMethods( |
- const std::vector<std::string>& requested_input_methods, |
- std::vector<std::string>* out_filtered_input_methods) { |
- out_filtered_input_methods->clear(); |
- for (size_t i = 0; i < requested_input_methods.size(); ++i) { |
- const std::string& input_method = requested_input_methods[i]; |
- if (whitelist_.InputMethodIdIsWhitelisted(input_method.c_str())) { |
- if (!InputMethodUtil::IsKeyboardLayout(input_method)) { |
- out_filtered_input_methods->push_back(input_method); |
- } |
- } else { |
- LOG(ERROR) << "Unsupported input method: " << input_method; |
- } |
- } |
- } |
- |
- // Frees input method names in |engines| and the list itself. Please make sure |
- // that |engines| points the head of the list. |
- void FreeInputMethodNames(GList* engines) { |
- if (engines) { |
- for (GList* cursor = engines; cursor; cursor = g_list_next(cursor)) { |
- g_object_unref(IBUS_ENGINE_DESC(cursor->data)); |
- } |
- g_list_free(engines); |
- } |
- } |
- |
- // Copies input method names in |engines| to |out|. |
- // TODO(yusukes): Write unittest. |
- void AddInputMethodNames(const GList* engines, InputMethodDescriptors* out) { |
- DCHECK(out); |
- for (; engines; engines = g_list_next(engines)) { |
- IBusEngineDesc* engine_desc = IBUS_ENGINE_DESC(engines->data); |
- const gchar* name = ibus_engine_desc_get_name(engine_desc); |
- const gchar* layout = ibus_engine_desc_get_layout(engine_desc); |
- const gchar* language = ibus_engine_desc_get_language(engine_desc); |
- if (whitelist_.InputMethodIdIsWhitelisted(name)) { |
- out->push_back(CreateInputMethodDescriptor(name, "", layout, language)); |
- VLOG(1) << name << " (preloaded)"; |
- } |
- } |
- } |
- |
- // A callback function that will be called when ibus_config_set_value_async() |
- // request is finished. |
- static void SetInputMethodConfigCallback(GObject* source_object, |
- GAsyncResult* res, |
- gpointer user_data) { |
- IBusConfig* config = IBUS_CONFIG(user_data); |
- g_return_if_fail(config); |
- |
- GError* error = NULL; |
- const gboolean result = |
- ibus_config_set_value_async_finish(config, res, &error); |
- |
- if (!result) { |
- std::string message = "(unknown error)"; |
- if (error && error->message) { |
- message = error->message; |
- } |
- LOG(ERROR) << "ibus_config_set_value_async failed: " << message; |
- } |
- |
- if (error) { |
- g_error_free(error); |
- } |
- g_object_unref(config); |
- } |
- |
- // Connection to the ibus-daemon via IBus API. These objects are used to |
- // call ibus-daemon's API (e.g. activate input methods, set config, ...) |
- IBusBus* ibus_; |
- IBusConfig* ibus_config_; |
- |
- // Current input context path. |
- std::string input_context_path_; |
- |
- ObserverList<Observer> observers_; |
- |
- // An object which knows all valid input method and layout IDs. |
- InputMethodWhitelist whitelist_; |
- |
- DISALLOW_COPY_AND_ASSIGN(IBusControllerImpl); |
-}; |
- |
-#endif // defined(HAVE_IBUS) |
- |
-// The stub implementation is used if IBus is not present. |
-// |
-// Note that this class is intentionally built even if HAVE_IBUS is |
-// defined so that we can easily tell build breakage when we change the |
-// IBusControllerImpl but forget to update the stub implementation. |
-class IBusControllerStubImpl : public IBusController { |
- public: |
- IBusControllerStubImpl() { |
- } |
- |
- virtual void Connect() { |
- }; |
- |
- virtual void AddObserver(Observer* observer) { |
- } |
- |
- virtual void RemoveObserver(Observer* observer) { |
- } |
- |
- virtual bool StopInputMethodProcess() { |
- return true; |
- } |
- |
- virtual bool ChangeInputMethod(const std::string& name) { |
- return true; |
- } |
- |
- virtual void SetImePropertyActivated(const std::string& key, |
- bool activated) { |
- } |
- |
- virtual bool SetInputMethodConfig(const std::string& section, |
- const std::string& config_name, |
- const InputMethodConfigValue& value) { |
- return true; |
- } |
- |
- virtual void SendHandwritingStroke(const HandwritingStroke& stroke) { |
- } |
- |
- virtual void CancelHandwriting(int n_strokes) { |
- } |
- |
- // This is for ibus_controller_unittest.cc. Since the test is usually compiled |
- // without HAVE_IBUS, we have to provide the same implementation as |
- // IBusControllerImpl to test the whitelist class. |
- virtual bool InputMethodIdIsWhitelisted(const std::string& input_method_id) { |
- return whitelist_.InputMethodIdIsWhitelisted(input_method_id); |
- } |
- // See the comment above. We have to keep the implementation the same as |
- // IBusControllerImpl. |
- virtual bool XkbLayoutIsSupported(const std::string& xkb_layout) { |
- return whitelist_.XkbLayoutIsSupported(xkb_layout); |
- } |
- // See the comment above. We have to keep the implementation the same as |
- // IBusControllerImpl. |
- virtual InputMethodDescriptor CreateInputMethodDescriptor( |
- const std::string& id, |
- const std::string& name, |
- const std::string& raw_layout, |
- const std::string& language_code) { |
- return InputMethodDescriptor(whitelist_, id, name, raw_layout, |
- language_code); |
- } |
- |
- private: |
- InputMethodWhitelist whitelist_; |
- |
- DISALLOW_COPY_AND_ASSIGN(IBusControllerStubImpl); |
-}; |
- |
+// static |
IBusController* IBusController::Create() { |
#if defined(HAVE_IBUS) |
return new IBusControllerImpl; |
#else |
- return new IBusControllerStubImpl; |
+ return new MockIBusController; |
#endif |
} |
-IBusController::~IBusController() { |
-} |
- |
} // namespace input_method |
} // namespace chromeos |