| OLD | NEW |
| 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "chrome/browser/chromeos/input_method/ibus_controller.h" | 5 #include "chrome/browser/chromeos/input_method/ibus_controller.h" |
| 6 | 6 |
| 7 #if defined(HAVE_IBUS) | 7 #if defined(HAVE_IBUS) |
| 8 #include <ibus.h> | 8 #include "chrome/browser/chromeos/input_method/ibus_controller_impl.h" |
| 9 #else |
| 10 #include "chrome/browser/chromeos/input_method/mock_ibus_controller.h" |
| 9 #endif | 11 #endif |
| 10 | 12 |
| 11 #include <algorithm> // for std::reverse. | |
| 12 #include <cstdio> | |
| 13 #include <cstring> // for std::strcmp. | |
| 14 #include <set> | |
| 15 #include <sstream> | |
| 16 #include <stack> | |
| 17 #include <utility> | |
| 18 | |
| 19 #include "base/memory/scoped_ptr.h" | |
| 20 #include "base/observer_list.h" | |
| 21 #include "chrome/browser/chromeos/input_method/input_method_engine.h" | |
| 22 #include "chrome/browser/chromeos/input_method/input_method_manager.h" | |
| 23 #include "chrome/browser/chromeos/input_method/input_method_util.h" | |
| 24 #include "chrome/browser/chromeos/input_method/input_method_whitelist.h" | |
| 25 #include "chrome/browser/chromeos/input_method/input_methods.h" | |
| 26 | |
| 27 namespace chromeos { | 13 namespace chromeos { |
| 28 namespace input_method { | 14 namespace input_method { |
| 29 | 15 |
| 30 #if defined(HAVE_IBUS) | 16 IBusController::~IBusController() { |
| 31 const char kPanelObjectKey[] = "panel-object"; | |
| 32 | |
| 33 namespace { | |
| 34 | |
| 35 // Also defined in chrome/browser/chromeos/language_preferences.h (Chrome tree). | |
| 36 const char kGeneralSectionName[] = "general"; | |
| 37 const char kPreloadEnginesConfigName[] = "preload_engines"; | |
| 38 | |
| 39 // The list of input method property keys that we don't handle. | |
| 40 const char* kInputMethodPropertyKeysBlacklist[] = { | |
| 41 "setup", // menu for showing setup dialog used in anthy and hangul. | |
| 42 "chewing_settings_prop", // menu for showing setup dialog used in chewing. | |
| 43 "status", // used in m17n. | |
| 44 }; | |
| 45 | |
| 46 const char* Or(const char* str1, const char* str2) { | |
| 47 return str1 ? str1 : str2; | |
| 48 } | 17 } |
| 49 | 18 |
| 50 // Returns true if |key| is blacklisted. | 19 // static |
| 51 bool PropertyKeyIsBlacklisted(const char* key) { | |
| 52 for (size_t i = 0; i < arraysize(kInputMethodPropertyKeysBlacklist); ++i) { | |
| 53 if (!std::strcmp(key, kInputMethodPropertyKeysBlacklist[i])) { | |
| 54 return true; | |
| 55 } | |
| 56 } | |
| 57 return false; | |
| 58 } | |
| 59 | |
| 60 // Returns IBusInputContext for |input_context_path|. NULL on errors. | |
| 61 IBusInputContext* GetInputContext( | |
| 62 const std::string& input_context_path, IBusBus* ibus) { | |
| 63 GDBusConnection* connection = ibus_bus_get_connection(ibus); | |
| 64 if (!connection) { | |
| 65 LOG(ERROR) << "IBusConnection is null"; | |
| 66 return NULL; | |
| 67 } | |
| 68 // This function does not issue an IBus IPC. | |
| 69 IBusInputContext* context = ibus_input_context_get_input_context( | |
| 70 input_context_path.c_str(), connection); | |
| 71 if (!context) { | |
| 72 LOG(ERROR) << "IBusInputContext is null: " << input_context_path; | |
| 73 } | |
| 74 return context; | |
| 75 } | |
| 76 | |
| 77 // Returns true if |prop| has children. | |
| 78 bool PropertyHasChildren(IBusProperty* prop) { | |
| 79 return prop && ibus_property_get_sub_props(prop) && | |
| 80 ibus_prop_list_get( | |
| 81 // TODO(yusukes): Remove the cast when we migrate to ibus-1.5. | |
| 82 const_cast<IBusPropList*>(ibus_property_get_sub_props(prop)), 0); | |
| 83 } | |
| 84 | |
| 85 // This function is called by and FlattenProperty() and converts IBus | |
| 86 // representation of a property, |ibus_prop|, to our own and push_back the | |
| 87 // result to |out_prop_list|. This function returns true on success, and | |
| 88 // returns false if sanity checks for |ibus_prop| fail. | |
| 89 bool ConvertProperty(IBusProperty* ibus_prop, | |
| 90 int selection_item_id, | |
| 91 InputMethodPropertyList* out_prop_list) { | |
| 92 DCHECK(ibus_prop); | |
| 93 DCHECK(out_prop_list); | |
| 94 | |
| 95 const IBusPropType type = ibus_property_get_prop_type(ibus_prop); | |
| 96 const IBusPropState state = ibus_property_get_state(ibus_prop); | |
| 97 const IBusText* tooltip = ibus_property_get_tooltip(ibus_prop); | |
| 98 const IBusText* label = ibus_property_get_label(ibus_prop); | |
| 99 const gchar* key = ibus_property_get_key(ibus_prop); | |
| 100 DCHECK(key); | |
| 101 | |
| 102 // Sanity checks. | |
| 103 const bool has_sub_props = PropertyHasChildren(ibus_prop); | |
| 104 if (has_sub_props && (type != PROP_TYPE_MENU)) { | |
| 105 LOG(ERROR) << "The property has sub properties, " | |
| 106 << "but the type of the property is not PROP_TYPE_MENU"; | |
| 107 return false; | |
| 108 } | |
| 109 if ((!has_sub_props) && (type == PROP_TYPE_MENU)) { | |
| 110 // This is usually not an error. ibus-daemon sometimes sends empty props. | |
| 111 VLOG(1) << "Property list is empty"; | |
| 112 return false; | |
| 113 } | |
| 114 if (type == PROP_TYPE_SEPARATOR || type == PROP_TYPE_MENU) { | |
| 115 // This is not an error, but we don't push an item for these types. | |
| 116 return true; | |
| 117 } | |
| 118 | |
| 119 const bool is_selection_item = (type == PROP_TYPE_RADIO); | |
| 120 selection_item_id = is_selection_item ? | |
| 121 selection_item_id : InputMethodProperty::kInvalidSelectionItemId; | |
| 122 | |
| 123 bool is_selection_item_checked = false; | |
| 124 if (state == PROP_STATE_INCONSISTENT) { | |
| 125 LOG(WARNING) << "The property is in PROP_STATE_INCONSISTENT, " | |
| 126 << "which is not supported."; | |
| 127 } else if ((!is_selection_item) && (state == PROP_STATE_CHECKED)) { | |
| 128 LOG(WARNING) << "PROP_STATE_CHECKED is meaningful only if the type is " | |
| 129 << "PROP_TYPE_RADIO."; | |
| 130 } else { | |
| 131 is_selection_item_checked = (state == PROP_STATE_CHECKED); | |
| 132 } | |
| 133 | |
| 134 if (!key) { | |
| 135 LOG(ERROR) << "key is NULL"; | |
| 136 } | |
| 137 if (tooltip && !tooltip->text) { | |
| 138 LOG(ERROR) << "tooltip is NOT NULL, but tooltip->text IS NULL: key=" | |
| 139 << Or(key, ""); | |
| 140 } | |
| 141 if (label && !label->text) { | |
| 142 LOG(ERROR) << "label is NOT NULL, but label->text IS NULL: key=" | |
| 143 << Or(key, ""); | |
| 144 } | |
| 145 | |
| 146 // This label will be localized on Chrome side. | |
| 147 // See src/chrome/browser/chromeos/status/language_menu_l10n_util.h. | |
| 148 std::string label_to_use = ((tooltip && tooltip->text) ? tooltip->text : ""); | |
| 149 if (label_to_use.empty()) { | |
| 150 // Usually tooltips are more descriptive than labels. | |
| 151 label_to_use = (label && label->text) ? label->text : ""; | |
| 152 } | |
| 153 if (label_to_use.empty()) { | |
| 154 // ibus-pinyin has a property whose label and tooltip are empty. Fall back | |
| 155 // to the key. | |
| 156 label_to_use = Or(key, ""); | |
| 157 } | |
| 158 | |
| 159 out_prop_list->push_back(InputMethodProperty(key, | |
| 160 label_to_use, | |
| 161 is_selection_item, | |
| 162 is_selection_item_checked, | |
| 163 selection_item_id)); | |
| 164 return true; | |
| 165 } | |
| 166 | |
| 167 // Converts |ibus_prop| to |out_prop_list|. Please note that |ibus_prop| | |
| 168 // may or may not have children. See the comment for FlattenPropertyList | |
| 169 // for details. Returns true if no error is found. | |
| 170 // TODO(yusukes): Write unittest. | |
| 171 bool FlattenProperty(IBusProperty* ibus_prop, | |
| 172 InputMethodPropertyList* out_prop_list) { | |
| 173 DCHECK(ibus_prop); | |
| 174 DCHECK(out_prop_list); | |
| 175 | |
| 176 int selection_item_id = -1; | |
| 177 std::stack<std::pair<IBusProperty*, int> > prop_stack; | |
| 178 prop_stack.push(std::make_pair(ibus_prop, selection_item_id)); | |
| 179 | |
| 180 while (!prop_stack.empty()) { | |
| 181 IBusProperty* prop = prop_stack.top().first; | |
| 182 const gchar* key = ibus_property_get_key(prop); | |
| 183 const int current_selection_item_id = prop_stack.top().second; | |
| 184 prop_stack.pop(); | |
| 185 | |
| 186 // Filter out unnecessary properties. | |
| 187 if (PropertyKeyIsBlacklisted(key)) { | |
| 188 continue; | |
| 189 } | |
| 190 | |
| 191 // Convert |prop| to InputMethodProperty and push it to |out_prop_list|. | |
| 192 if (!ConvertProperty(prop, current_selection_item_id, out_prop_list)) { | |
| 193 return false; | |
| 194 } | |
| 195 | |
| 196 // Process childrens iteratively (if any). Push all sub properties to the | |
| 197 // stack. | |
| 198 if (PropertyHasChildren(prop)) { | |
| 199 ++selection_item_id; | |
| 200 for (int i = 0;; ++i) { | |
| 201 IBusProperty* sub_prop = ibus_prop_list_get( | |
| 202 // TODO(yusukes): Remove the cast when we migrate to ibus-1.5. | |
| 203 const_cast<IBusPropList*>(ibus_property_get_sub_props(prop)), i); | |
| 204 if (!sub_prop) { | |
| 205 break; | |
| 206 } | |
| 207 prop_stack.push(std::make_pair(sub_prop, selection_item_id)); | |
| 208 } | |
| 209 ++selection_item_id; | |
| 210 } | |
| 211 } | |
| 212 std::reverse(out_prop_list->begin(), out_prop_list->end()); | |
| 213 | |
| 214 return true; | |
| 215 } | |
| 216 | |
| 217 // Converts IBus representation of a property list, |ibus_prop_list| to our | |
| 218 // own. This function also flatten the original list (actually it's a tree). | |
| 219 // Returns true if no error is found. The conversion to our own type is | |
| 220 // necessary since our language switcher in Chrome tree don't (or can't) know | |
| 221 // IBus types. Here is an example: | |
| 222 // | |
| 223 // ====================================================================== | |
| 224 // Input: | |
| 225 // | |
| 226 // --- Item-1 | |
| 227 // |- Item-2 | |
| 228 // |- SubMenuRoot --- Item-3-1 | |
| 229 // | |- Item-3-2 | |
| 230 // | |- Item-3-3 | |
| 231 // |- Item-4 | |
| 232 // | |
| 233 // (Note: Item-3-X is a selection item since they're on a sub menu.) | |
| 234 // | |
| 235 // Output: | |
| 236 // | |
| 237 // Item-1, Item-2, Item-3-1, Item-3-2, Item-3-3, Item-4 | |
| 238 // (Note: SubMenuRoot does not appear in the output.) | |
| 239 // ====================================================================== | |
| 240 // TODO(yusukes): Write unittest. | |
| 241 bool FlattenPropertyList( | |
| 242 IBusPropList* ibus_prop_list, InputMethodPropertyList* out_prop_list) { | |
| 243 DCHECK(ibus_prop_list); | |
| 244 DCHECK(out_prop_list); | |
| 245 | |
| 246 IBusProperty* fake_root_prop = ibus_property_new("Dummy.Key", | |
| 247 PROP_TYPE_MENU, | |
| 248 NULL, /* label */ | |
| 249 "", /* icon */ | |
| 250 NULL, /* tooltip */ | |
| 251 FALSE, /* sensitive */ | |
| 252 FALSE, /* visible */ | |
| 253 PROP_STATE_UNCHECKED, | |
| 254 ibus_prop_list); | |
| 255 g_return_val_if_fail(fake_root_prop, false); | |
| 256 // Increase the ref count so it won't get deleted when |fake_root_prop| | |
| 257 // is deleted. | |
| 258 g_object_ref(ibus_prop_list); | |
| 259 const bool result = FlattenProperty(fake_root_prop, out_prop_list); | |
| 260 g_object_unref(fake_root_prop); | |
| 261 | |
| 262 return result; | |
| 263 } | |
| 264 | |
| 265 // Debug print function. | |
| 266 const char* PropTypeToString(int prop_type) { | |
| 267 switch (static_cast<IBusPropType>(prop_type)) { | |
| 268 case PROP_TYPE_NORMAL: | |
| 269 return "NORMAL"; | |
| 270 case PROP_TYPE_TOGGLE: | |
| 271 return "TOGGLE"; | |
| 272 case PROP_TYPE_RADIO: | |
| 273 return "RADIO"; | |
| 274 case PROP_TYPE_MENU: | |
| 275 return "MENU"; | |
| 276 case PROP_TYPE_SEPARATOR: | |
| 277 return "SEPARATOR"; | |
| 278 } | |
| 279 return "UNKNOWN"; | |
| 280 } | |
| 281 | |
| 282 // Debug print function. | |
| 283 const char* PropStateToString(int prop_state) { | |
| 284 switch (static_cast<IBusPropState>(prop_state)) { | |
| 285 case PROP_STATE_UNCHECKED: | |
| 286 return "UNCHECKED"; | |
| 287 case PROP_STATE_CHECKED: | |
| 288 return "CHECKED"; | |
| 289 case PROP_STATE_INCONSISTENT: | |
| 290 return "INCONSISTENT"; | |
| 291 } | |
| 292 return "UNKNOWN"; | |
| 293 } | |
| 294 | |
| 295 // Debug print function. | |
| 296 std::string Spacer(int n) { | |
| 297 return std::string(n, ' '); | |
| 298 } | |
| 299 | |
| 300 std::string PrintPropList(IBusPropList *prop_list, int tree_level); | |
| 301 // Debug print function. | |
| 302 std::string PrintProp(IBusProperty *prop, int tree_level) { | |
| 303 if (!prop) { | |
| 304 return ""; | |
| 305 } | |
| 306 | |
| 307 const IBusPropType type = ibus_property_get_prop_type(prop); | |
| 308 const IBusPropState state = ibus_property_get_state(prop); | |
| 309 const IBusText* tooltip = ibus_property_get_tooltip(prop); | |
| 310 const IBusText* label = ibus_property_get_label(prop); | |
| 311 const gchar* key = ibus_property_get_key(prop); | |
| 312 | |
| 313 std::stringstream stream; | |
| 314 stream << Spacer(tree_level) << "=========================" << std::endl; | |
| 315 stream << Spacer(tree_level) << "key: " << Or(key, "<none>") | |
| 316 << std::endl; | |
| 317 stream << Spacer(tree_level) << "label: " | |
| 318 << ((label && label->text) ? label->text : "<none>") | |
| 319 << std::endl; | |
| 320 stream << Spacer(tree_level) << "tooptip: " | |
| 321 << ((tooltip && tooltip->text) | |
| 322 ? tooltip->text : "<none>") << std::endl; | |
| 323 stream << Spacer(tree_level) << "sensitive: " | |
| 324 << (ibus_property_get_sensitive(prop) ? "YES" : "NO") << std::endl; | |
| 325 stream << Spacer(tree_level) << "visible: " | |
| 326 << (ibus_property_get_visible(prop) ? "YES" : "NO") << std::endl; | |
| 327 stream << Spacer(tree_level) << "type: " << PropTypeToString(type) | |
| 328 << std::endl; | |
| 329 stream << Spacer(tree_level) << "state: " << PropStateToString(state) | |
| 330 << std::endl; | |
| 331 stream << Spacer(tree_level) << "sub_props: " | |
| 332 << (PropertyHasChildren(prop) ? "" : "<none>") << std::endl; | |
| 333 stream << PrintPropList( | |
| 334 // TODO(yusukes): Remove the cast when we migrate to ibus-1.5. | |
| 335 const_cast<IBusPropList*>(ibus_property_get_sub_props(prop)), | |
| 336 tree_level + 1); | |
| 337 stream << Spacer(tree_level) << "=========================" << std::endl; | |
| 338 | |
| 339 return stream.str(); | |
| 340 } | |
| 341 | |
| 342 // Debug print function. | |
| 343 std::string PrintPropList(IBusPropList *prop_list, int tree_level) { | |
| 344 if (!prop_list) { | |
| 345 return ""; | |
| 346 } | |
| 347 | |
| 348 std::stringstream stream; | |
| 349 for (int i = 0;; ++i) { | |
| 350 IBusProperty* prop = ibus_prop_list_get(prop_list, i); | |
| 351 if (!prop) { | |
| 352 break; | |
| 353 } | |
| 354 stream << PrintProp(prop, tree_level); | |
| 355 } | |
| 356 return stream.str(); | |
| 357 } | |
| 358 | |
| 359 } // namespace | |
| 360 | |
| 361 // The real implementation of the IBusController. | |
| 362 class IBusControllerImpl : public IBusController { | |
| 363 public: | |
| 364 IBusControllerImpl() | |
| 365 : ibus_(NULL), | |
| 366 ibus_config_(NULL) { | |
| 367 } | |
| 368 | |
| 369 ~IBusControllerImpl() { | |
| 370 // Disconnect signals so the handler functions will not be called with | |
| 371 // |this| which is already freed. | |
| 372 if (ibus_) { | |
| 373 g_signal_handlers_disconnect_by_func( | |
| 374 ibus_, | |
| 375 reinterpret_cast<gpointer>(G_CALLBACK(IBusBusConnectedThunk)), | |
| 376 this); | |
| 377 g_signal_handlers_disconnect_by_func( | |
| 378 ibus_, | |
| 379 reinterpret_cast<gpointer>(G_CALLBACK(IBusBusDisconnectedThunk)), | |
| 380 this); | |
| 381 g_signal_handlers_disconnect_by_func( | |
| 382 ibus_, | |
| 383 reinterpret_cast<gpointer>(G_CALLBACK(IBusBusNameOwnerChangedThunk)), | |
| 384 this); | |
| 385 | |
| 386 // Disconnect signals for the panel service as well. | |
| 387 IBusPanelService* ibus_panel_service = IBUS_PANEL_SERVICE( | |
| 388 g_object_get_data(G_OBJECT(ibus_), kPanelObjectKey)); | |
| 389 if (ibus_panel_service) { | |
| 390 g_signal_handlers_disconnect_by_func( | |
| 391 ibus_panel_service, | |
| 392 reinterpret_cast<gpointer>(G_CALLBACK(FocusInThunk)), | |
| 393 this); | |
| 394 g_signal_handlers_disconnect_by_func( | |
| 395 ibus_panel_service, | |
| 396 reinterpret_cast<gpointer>(G_CALLBACK(RegisterPropertiesThunk)), | |
| 397 this); | |
| 398 g_signal_handlers_disconnect_by_func( | |
| 399 ibus_panel_service, | |
| 400 reinterpret_cast<gpointer>(G_CALLBACK(UpdatePropertyThunk)), | |
| 401 this); | |
| 402 } | |
| 403 } | |
| 404 } | |
| 405 | |
| 406 virtual void Connect() { | |
| 407 MaybeRestoreConnections(); | |
| 408 } | |
| 409 | |
| 410 // IBusController override. | |
| 411 virtual bool StopInputMethodProcess() { | |
| 412 if (!IBusConnectionsAreAlive()) { | |
| 413 LOG(ERROR) << "StopInputMethodProcess: IBus connection is not alive"; | |
| 414 return false; | |
| 415 } | |
| 416 | |
| 417 // Ask IBus to exit *asynchronously*. | |
| 418 ibus_bus_exit_async(ibus_, | |
| 419 FALSE /* do not restart */, | |
| 420 -1 /* timeout */, | |
| 421 NULL /* cancellable */, | |
| 422 NULL /* callback */, | |
| 423 NULL /* user_data */); | |
| 424 | |
| 425 if (ibus_config_) { | |
| 426 // Release |ibus_config_| unconditionally to make sure next | |
| 427 // IBusConnectionsAreAlive() call will return false. | |
| 428 g_object_unref(ibus_config_); | |
| 429 ibus_config_ = NULL; | |
| 430 } | |
| 431 return true; | |
| 432 } | |
| 433 | |
| 434 // IBusController override. | |
| 435 virtual void SetImePropertyActivated(const std::string& key, | |
| 436 bool activated) { | |
| 437 if (!IBusConnectionsAreAlive()) { | |
| 438 LOG(ERROR) << "SetImePropertyActivated: IBus connection is not alive"; | |
| 439 return; | |
| 440 } | |
| 441 if (key.empty()) { | |
| 442 return; | |
| 443 } | |
| 444 if (input_context_path_.empty()) { | |
| 445 LOG(ERROR) << "Input context is unknown"; | |
| 446 return; | |
| 447 } | |
| 448 | |
| 449 IBusInputContext* context = GetInputContext(input_context_path_, ibus_); | |
| 450 if (!context) { | |
| 451 return; | |
| 452 } | |
| 453 // Activate the property *asynchronously*. | |
| 454 ibus_input_context_property_activate( | |
| 455 context, key.c_str(), | |
| 456 (activated ? PROP_STATE_CHECKED : PROP_STATE_UNCHECKED)); | |
| 457 | |
| 458 // We don't have to call ibus_proxy_destroy(context) explicitly here, | |
| 459 // i.e. we can just call g_object_unref(context), since g_object_unref can | |
| 460 // trigger both dispose, which is overridden by src/ibusproxy.c, and | |
| 461 // finalize functions. For details, see | |
| 462 // http://library.gnome.org/devel/gobject/stable/gobject-memory.html | |
| 463 g_object_unref(context); | |
| 464 } | |
| 465 | |
| 466 // IBusController override. | |
| 467 virtual bool ChangeInputMethod(const std::string& name) { | |
| 468 DCHECK(!InputMethodUtil::IsKeyboardLayout(name)); | |
| 469 | |
| 470 if (!IBusConnectionsAreAlive()) { | |
| 471 LOG(ERROR) << "ChangeInputMethod: IBus connection is not alive"; | |
| 472 return false; | |
| 473 } | |
| 474 if (name.empty()) { | |
| 475 return false; | |
| 476 } | |
| 477 if (!InputMethodIdIsWhitelisted(name) && | |
| 478 name.find(kExtensionImePrefix) != 0) { | |
| 479 return false; | |
| 480 } | |
| 481 | |
| 482 // Clear all input method properties unconditionally. | |
| 483 // | |
| 484 // When switching to another input method and no text area is focused, | |
| 485 // RegisterProperties signal for the new input method will NOT be sent | |
| 486 // until a text area is focused. Therefore, we have to clear the old input | |
| 487 // method properties here to keep the input method switcher status | |
| 488 // consistent. | |
| 489 DoRegisterProperties(NULL); | |
| 490 | |
| 491 // Change the global engine *asynchronously*. | |
| 492 ibus_bus_set_global_engine_async(ibus_, | |
| 493 name.c_str(), | |
| 494 -1, // use the default ibus timeout | |
| 495 NULL, // cancellable | |
| 496 NULL, // callback | |
| 497 NULL); // user_data | |
| 498 | |
| 499 UpdateUI(name.c_str()); | |
| 500 return true; | |
| 501 } | |
| 502 | |
| 503 // IBusController override. | |
| 504 virtual bool SetInputMethodConfig(const std::string& section, | |
| 505 const std::string& config_name, | |
| 506 const InputMethodConfigValue& value) { | |
| 507 // See comments in GetImeConfig() where ibus_config_get_value() is used. | |
| 508 if (!IBusConnectionsAreAlive()) { | |
| 509 LOG(ERROR) << "SetInputMethodConfig: IBus connection is not alive"; | |
| 510 return false; | |
| 511 } | |
| 512 | |
| 513 bool is_preload_engines = false; | |
| 514 | |
| 515 // Sanity check: do not preload unknown/unsupported input methods. | |
| 516 std::vector<std::string> string_list; | |
| 517 if ((value.type == InputMethodConfigValue::kValueTypeStringList) && | |
| 518 (section == kGeneralSectionName) && | |
| 519 (config_name == kPreloadEnginesConfigName)) { | |
| 520 FilterInputMethods(value.string_list_value, &string_list); | |
| 521 if (string_list.empty()) { | |
| 522 return true; | |
| 523 } | |
| 524 is_preload_engines = true; | |
| 525 } else { | |
| 526 string_list = value.string_list_value; | |
| 527 } | |
| 528 | |
| 529 // Convert the type of |value| from our structure to GVariant. | |
| 530 GVariant* variant = NULL; | |
| 531 switch (value.type) { | |
| 532 case InputMethodConfigValue::kValueTypeString: | |
| 533 variant = g_variant_new_string(value.string_value.c_str()); | |
| 534 break; | |
| 535 case InputMethodConfigValue::kValueTypeInt: | |
| 536 variant = g_variant_new_int32(value.int_value); | |
| 537 break; | |
| 538 case InputMethodConfigValue::kValueTypeBool: | |
| 539 variant = g_variant_new_boolean(value.bool_value); | |
| 540 break; | |
| 541 case InputMethodConfigValue::kValueTypeStringList: | |
| 542 GVariantBuilder variant_builder; | |
| 543 g_variant_builder_init(&variant_builder, G_VARIANT_TYPE("as")); | |
| 544 DCHECK(!string_list.empty()); | |
| 545 const size_t size = string_list.size(); // don't use string_list_value. | |
| 546 for (size_t i = 0; i < size; ++i) { | |
| 547 g_variant_builder_add(&variant_builder, "s", string_list[i].c_str()); | |
| 548 } | |
| 549 variant = g_variant_builder_end(&variant_builder); | |
| 550 break; | |
| 551 } | |
| 552 | |
| 553 if (!variant) { | |
| 554 LOG(ERROR) << "SetInputMethodConfig: variant is NULL"; | |
| 555 return false; | |
| 556 } | |
| 557 DCHECK(g_variant_is_floating(variant)); | |
| 558 | |
| 559 // Set an ibus configuration value *asynchronously*. | |
| 560 ibus_config_set_value_async(ibus_config_, | |
| 561 section.c_str(), | |
| 562 config_name.c_str(), | |
| 563 variant, | |
| 564 -1, // use the default ibus timeout | |
| 565 NULL, // cancellable | |
| 566 SetInputMethodConfigCallback, | |
| 567 g_object_ref(ibus_config_)); | |
| 568 | |
| 569 // Since |variant| is floating, ibus_config_set_value_async consumes | |
| 570 // (takes ownership of) the variable. | |
| 571 | |
| 572 if (is_preload_engines) { | |
| 573 VLOG(1) << "SetInputMethodConfig: " << section << "/" << config_name | |
| 574 << ": " << value.ToString(); | |
| 575 } | |
| 576 return true; | |
| 577 } | |
| 578 | |
| 579 // IBusController override. | |
| 580 virtual void SendHandwritingStroke(const HandwritingStroke& stroke) { | |
| 581 if (stroke.size() < 2) { | |
| 582 LOG(WARNING) << "Empty stroke data or a single dot is passed."; | |
| 583 return; | |
| 584 } | |
| 585 | |
| 586 IBusInputContext* context = GetInputContext(input_context_path_, ibus_); | |
| 587 if (!context) { | |
| 588 return; | |
| 589 } | |
| 590 | |
| 591 const size_t raw_stroke_size = stroke.size() * 2; | |
| 592 scoped_array<double> raw_stroke(new double[raw_stroke_size]); | |
| 593 for (size_t n = 0; n < stroke.size(); ++n) { | |
| 594 raw_stroke[n * 2] = stroke[n].first; // x | |
| 595 raw_stroke[n * 2 + 1] = stroke[n].second; // y | |
| 596 } | |
| 597 ibus_input_context_process_hand_writing_event( | |
| 598 context, raw_stroke.get(), raw_stroke_size); | |
| 599 g_object_unref(context); | |
| 600 } | |
| 601 | |
| 602 // IBusController override. | |
| 603 virtual void CancelHandwriting(int n_strokes) { | |
| 604 IBusInputContext* context = GetInputContext(input_context_path_, ibus_); | |
| 605 if (!context) { | |
| 606 return; | |
| 607 } | |
| 608 ibus_input_context_cancel_hand_writing(context, n_strokes); | |
| 609 g_object_unref(context); | |
| 610 } | |
| 611 | |
| 612 virtual bool InputMethodIdIsWhitelisted(const std::string& input_method_id) { | |
| 613 return whitelist_.InputMethodIdIsWhitelisted(input_method_id); | |
| 614 } | |
| 615 | |
| 616 virtual bool XkbLayoutIsSupported(const std::string& xkb_layout) { | |
| 617 return whitelist_.XkbLayoutIsSupported(xkb_layout); | |
| 618 } | |
| 619 | |
| 620 virtual InputMethodDescriptor CreateInputMethodDescriptor( | |
| 621 const std::string& id, | |
| 622 const std::string& name, | |
| 623 const std::string& raw_layout, | |
| 624 const std::string& language_code) { | |
| 625 return InputMethodDescriptor(whitelist_, id, name, raw_layout, | |
| 626 language_code); | |
| 627 } | |
| 628 | |
| 629 // IBusController override. | |
| 630 virtual void AddObserver(Observer* observer) { | |
| 631 observers_.AddObserver(observer); | |
| 632 } | |
| 633 | |
| 634 // IBusController override. | |
| 635 virtual void RemoveObserver(Observer* observer) { | |
| 636 observers_.RemoveObserver(observer); | |
| 637 } | |
| 638 | |
| 639 private: | |
| 640 // Functions that end with Thunk are used to deal with glib callbacks. | |
| 641 // | |
| 642 // Note that we cannot use CHROMEG_CALLBACK_0() here as we'll define | |
| 643 // IBusBusConnected() inline. If we are to define the function outside | |
| 644 // of the class definition, we should use CHROMEG_CALLBACK_0() here. | |
| 645 // | |
| 646 // CHROMEG_CALLBACK_0(Impl, | |
| 647 // void, IBusBusConnected, IBusBus*); | |
| 648 static void IBusBusConnectedThunk(IBusBus* sender, gpointer userdata) { | |
| 649 return reinterpret_cast<IBusControllerImpl*>(userdata) | |
| 650 ->IBusBusConnected(sender); | |
| 651 } | |
| 652 static void IBusBusDisconnectedThunk(IBusBus* sender, gpointer userdata) { | |
| 653 return reinterpret_cast<IBusControllerImpl*>(userdata) | |
| 654 ->IBusBusDisconnected(sender); | |
| 655 } | |
| 656 | |
| 657 static void IBusBusNameOwnerChangedThunk(IBusBus* sender, | |
| 658 const gchar* name, | |
| 659 const gchar* old_name, | |
| 660 const gchar* new_name, | |
| 661 gpointer userdata) { | |
| 662 return reinterpret_cast<IBusControllerImpl*>(userdata) | |
| 663 ->IBusBusNameOwnerChanged(sender, name, old_name, new_name); | |
| 664 } | |
| 665 static void FocusInThunk(IBusPanelService* sender, | |
| 666 const gchar* input_context_path, | |
| 667 gpointer userdata) { | |
| 668 return reinterpret_cast<IBusControllerImpl*>(userdata) | |
| 669 ->FocusIn(sender, input_context_path); | |
| 670 } | |
| 671 static void RegisterPropertiesThunk(IBusPanelService* sender, | |
| 672 IBusPropList* prop_list, | |
| 673 gpointer userdata) { | |
| 674 return reinterpret_cast<IBusControllerImpl*>(userdata) | |
| 675 ->RegisterProperties(sender, prop_list); | |
| 676 } | |
| 677 static void UpdatePropertyThunk(IBusPanelService* sender, | |
| 678 IBusProperty* ibus_prop, | |
| 679 gpointer userdata) { | |
| 680 return reinterpret_cast<IBusControllerImpl*>(userdata) | |
| 681 ->UpdateProperty(sender, ibus_prop); | |
| 682 } | |
| 683 | |
| 684 // Checks if |ibus_| and |ibus_config_| connections are alive. | |
| 685 bool IBusConnectionsAreAlive() { | |
| 686 return ibus_ && ibus_bus_is_connected(ibus_) && ibus_config_; | |
| 687 } | |
| 688 | |
| 689 // Restores connections to ibus-daemon and ibus-memconf if they are not ready. | |
| 690 // If both |ibus_| and |ibus_config_| become ready, the function sends a | |
| 691 // notification to Chrome. | |
| 692 void MaybeRestoreConnections() { | |
| 693 if (IBusConnectionsAreAlive()) { | |
| 694 return; | |
| 695 } | |
| 696 MaybeCreateIBus(); | |
| 697 MaybeRestoreIBusConfig(); | |
| 698 if (IBusConnectionsAreAlive()) { | |
| 699 ConnectPanelServiceSignals(); | |
| 700 VLOG(1) << "Notifying Chrome that IBus is ready."; | |
| 701 FOR_EACH_OBSERVER(Observer, observers_, OnConnectionChange(true)); | |
| 702 } | |
| 703 } | |
| 704 | |
| 705 // Creates IBusBus object if it's not created yet. | |
| 706 void MaybeCreateIBus() { | |
| 707 if (ibus_) { | |
| 708 return; | |
| 709 } | |
| 710 | |
| 711 ibus_init(); | |
| 712 // Establish IBus connection between ibus-daemon to retrieve the list of | |
| 713 // available input method engines, change the current input method engine, | |
| 714 // and so on. | |
| 715 ibus_ = ibus_bus_new(); | |
| 716 | |
| 717 // Check the IBus connection status. | |
| 718 if (!ibus_) { | |
| 719 LOG(ERROR) << "ibus_bus_new() failed"; | |
| 720 return; | |
| 721 } | |
| 722 // Register callback functions for IBusBus signals. | |
| 723 ConnectIBusSignals(); | |
| 724 | |
| 725 // Ask libibus to watch the NameOwnerChanged signal *asynchronously*. | |
| 726 ibus_bus_set_watch_dbus_signal(ibus_, TRUE); | |
| 727 | |
| 728 if (ibus_bus_is_connected(ibus_)) { | |
| 729 VLOG(1) << "IBus connection is ready."; | |
| 730 } | |
| 731 } | |
| 732 | |
| 733 // Creates IBusConfig object if it's not created yet AND |ibus_| connection | |
| 734 // is ready. | |
| 735 void MaybeRestoreIBusConfig() { | |
| 736 if (!ibus_) { | |
| 737 return; | |
| 738 } | |
| 739 | |
| 740 // Destroy the current |ibus_config_| object. No-op if it's NULL. | |
| 741 MaybeDestroyIBusConfig(); | |
| 742 | |
| 743 if (!ibus_config_) { | |
| 744 GDBusConnection* ibus_connection = ibus_bus_get_connection(ibus_); | |
| 745 if (!ibus_connection) { | |
| 746 VLOG(1) << "Couldn't create an ibus config object since " | |
| 747 << "IBus connection is not ready."; | |
| 748 return; | |
| 749 } | |
| 750 const gboolean disconnected | |
| 751 = g_dbus_connection_is_closed(ibus_connection); | |
| 752 if (disconnected) { | |
| 753 // |ibus_| object is not NULL, but the connection between ibus-daemon | |
| 754 // is not yet established. In this case, we don't create |ibus_config_| | |
| 755 // object. | |
| 756 LOG(ERROR) << "Couldn't create an ibus config object since " | |
| 757 << "IBus connection is closed."; | |
| 758 return; | |
| 759 } | |
| 760 // If memconf is not successfully started yet, ibus_config_new() will | |
| 761 // return NULL. Otherwise, it returns a transfer-none and non-floating | |
| 762 // object. ibus_config_new() sometimes issues a D-Bus *synchronous* IPC | |
| 763 // to check if the org.freedesktop.IBus.Config service is available. | |
| 764 ibus_config_ = ibus_config_new(ibus_connection, | |
| 765 NULL /* do not cancel the operation */, | |
| 766 NULL /* do not get error information */); | |
| 767 if (!ibus_config_) { | |
| 768 LOG(ERROR) << "ibus_config_new() failed. ibus-memconf is not ready?"; | |
| 769 return; | |
| 770 } | |
| 771 | |
| 772 // TODO(yusukes): g_object_weak_ref might be better since it allows | |
| 773 // libcros to detect the delivery of the "destroy" glib signal the | |
| 774 // |ibus_config_| object. | |
| 775 g_object_ref(ibus_config_); | |
| 776 VLOG(1) << "ibus_config_ is ready."; | |
| 777 } | |
| 778 } | |
| 779 | |
| 780 // Destroys IBusConfig object if |ibus_| connection is not ready. This | |
| 781 // function does nothing if |ibus_config_| is NULL or |ibus_| connection is | |
| 782 // still alive. Note that the IBusConfig object can't be used when |ibus_| | |
| 783 // connection is not ready. | |
| 784 void MaybeDestroyIBusConfig() { | |
| 785 if (!ibus_) { | |
| 786 LOG(ERROR) << "MaybeDestroyIBusConfig: ibus_ is NULL"; | |
| 787 return; | |
| 788 } | |
| 789 if (ibus_config_ && !ibus_bus_is_connected(ibus_)) { | |
| 790 g_object_unref(ibus_config_); | |
| 791 ibus_config_ = NULL; | |
| 792 } | |
| 793 } | |
| 794 | |
| 795 // Handles "RegisterProperties" signal from chromeos_input_method_ui. | |
| 796 void DoRegisterProperties(IBusPropList* ibus_prop_list) { | |
| 797 VLOG(1) << "RegisterProperties" << (ibus_prop_list ? "" : " (clear)"); | |
| 798 | |
| 799 InputMethodPropertyList prop_list; // our representation. | |
| 800 if (ibus_prop_list) { | |
| 801 // You can call | |
| 802 // LOG(INFO) << "\n" << PrintPropList(ibus_prop_list, 0); | |
| 803 // here to dump |ibus_prop_list|. | |
| 804 if (!FlattenPropertyList(ibus_prop_list, &prop_list)) { | |
| 805 // Clear properties on errors. | |
| 806 DoRegisterProperties(NULL); | |
| 807 return; | |
| 808 } | |
| 809 } | |
| 810 VLOG(1) << "RegisterProperties" << (ibus_prop_list ? "" : " (clear)"); | |
| 811 // Notify the change. | |
| 812 FOR_EACH_OBSERVER(Observer, observers_, | |
| 813 OnRegisterImeProperties(prop_list)); | |
| 814 } | |
| 815 | |
| 816 // Retrieves input method status and notifies them to the UI. | |
| 817 // |current_global_engine_id| is the current global engine id such as "mozc" | |
| 818 // and "xkb:us::eng". If the id is unknown, an empty string "" can be passed. | |
| 819 // Warning: you can call this function only from ibus callback functions | |
| 820 // like FocusIn(). See http://crosbug.com/5217#c9 for details. | |
| 821 void UpdateUI(const char* current_global_engine_id) { | |
| 822 DCHECK(current_global_engine_id); | |
| 823 | |
| 824 const InputMethodsInfo* engine_info = NULL; | |
| 825 for (size_t i = 0; i < arraysize(kInputMethods); ++i) { | |
| 826 if (kInputMethods[i].input_method_id == | |
| 827 std::string(current_global_engine_id)) { | |
| 828 engine_info = &kInputMethods[i]; | |
| 829 break; | |
| 830 } | |
| 831 } | |
| 832 | |
| 833 InputMethodDescriptor current_input_method; | |
| 834 if (engine_info) { | |
| 835 current_input_method = CreateInputMethodDescriptor( | |
| 836 engine_info->input_method_id, | |
| 837 "", | |
| 838 engine_info->xkb_layout_id, | |
| 839 engine_info->language_code); | |
| 840 } else { | |
| 841 if (!InputMethodManager::GetInstance()->GetExtraDescriptor( | |
| 842 current_global_engine_id, ¤t_input_method)) { | |
| 843 LOG(ERROR) << current_global_engine_id | |
| 844 << " is not found in the input method white-list."; | |
| 845 return; | |
| 846 } | |
| 847 } | |
| 848 | |
| 849 | |
| 850 VLOG(1) << "Updating the UI. ID:" << current_input_method.id() | |
| 851 << ", keyboard_layout:" << current_input_method.keyboard_layout(); | |
| 852 | |
| 853 // Notify the change to update UI. | |
| 854 FOR_EACH_OBSERVER(Observer, observers_, | |
| 855 OnCurrentInputMethodChanged(current_input_method)); | |
| 856 } | |
| 857 | |
| 858 // Installs gobject signal handlers to |ibus_|. | |
| 859 void ConnectIBusSignals() { | |
| 860 if (!ibus_) { | |
| 861 return; | |
| 862 } | |
| 863 | |
| 864 // We use g_signal_connect_after here since the callback should be called | |
| 865 // *after* the IBusBusDisconnectedCallback in chromeos_input_method_ui.cc | |
| 866 // is called. chromeos_input_method_ui.cc attaches the panel service object | |
| 867 // to |ibus_|, and the callback in this file use the attached object. | |
| 868 g_signal_connect_after(ibus_, | |
| 869 "connected", | |
| 870 G_CALLBACK(IBusBusConnectedThunk), | |
| 871 this); | |
| 872 | |
| 873 g_signal_connect(ibus_, | |
| 874 "disconnected", | |
| 875 G_CALLBACK(IBusBusDisconnectedThunk), | |
| 876 this); | |
| 877 | |
| 878 g_signal_connect(ibus_, | |
| 879 "name-owner-changed", | |
| 880 G_CALLBACK(IBusBusNameOwnerChangedThunk), | |
| 881 this); | |
| 882 } | |
| 883 | |
| 884 // Installs gobject signal handlers to the panel service. | |
| 885 void ConnectPanelServiceSignals() { | |
| 886 if (!ibus_) { | |
| 887 return; | |
| 888 } | |
| 889 | |
| 890 IBusPanelService* ibus_panel_service = IBUS_PANEL_SERVICE( | |
| 891 g_object_get_data(G_OBJECT(ibus_), kPanelObjectKey)); | |
| 892 if (!ibus_panel_service) { | |
| 893 LOG(ERROR) << "IBusPanelService is NOT available."; | |
| 894 return; | |
| 895 } | |
| 896 // We don't _ref() or _weak_ref() the panel service object, since we're not | |
| 897 // interested in the life time of the object. | |
| 898 | |
| 899 g_signal_connect(ibus_panel_service, | |
| 900 "focus-in", | |
| 901 G_CALLBACK(FocusInThunk), | |
| 902 this); | |
| 903 g_signal_connect(ibus_panel_service, | |
| 904 "register-properties", | |
| 905 G_CALLBACK(RegisterPropertiesThunk), | |
| 906 this); | |
| 907 g_signal_connect(ibus_panel_service, | |
| 908 "update-property", | |
| 909 G_CALLBACK(UpdatePropertyThunk), | |
| 910 this); | |
| 911 } | |
| 912 | |
| 913 // Handles "connected" signal from ibus-daemon. | |
| 914 void IBusBusConnected(IBusBus* bus) { | |
| 915 LOG(WARNING) << "IBus connection is recovered."; | |
| 916 MaybeRestoreConnections(); | |
| 917 } | |
| 918 | |
| 919 // Handles "disconnected" signal from ibus-daemon. | |
| 920 void IBusBusDisconnected(IBusBus* bus) { | |
| 921 LOG(WARNING) << "IBus connection is terminated."; | |
| 922 // ibus-daemon might be terminated. Since |ibus_| object will automatically | |
| 923 // connect to the daemon if it restarts, we don't have to set NULL on ibus_. | |
| 924 // Call MaybeDestroyIBusConfig() to set |ibus_config_| to NULL temporarily. | |
| 925 MaybeDestroyIBusConfig(); | |
| 926 VLOG(1) << "Notifying Chrome that IBus is terminated."; | |
| 927 FOR_EACH_OBSERVER(Observer, observers_, OnConnectionChange(false)); | |
| 928 } | |
| 929 | |
| 930 // Handles "name-owner-changed" signal from ibus-daemon. The signal is sent | |
| 931 // to libcros when an IBus component such as ibus-memconf, ibus-engine-*, .. | |
| 932 // is started. | |
| 933 void IBusBusNameOwnerChanged(IBusBus* bus, | |
| 934 const gchar* name, | |
| 935 const gchar* old_name, | |
| 936 const gchar* new_name) { | |
| 937 DCHECK(name); | |
| 938 DCHECK(old_name); | |
| 939 DCHECK(new_name); | |
| 940 VLOG(1) << "Name owner is changed: name=" << name | |
| 941 << ", old_name=" << old_name << ", new_name=" << new_name; | |
| 942 | |
| 943 if (name != std::string("org.freedesktop.IBus.Config")) { | |
| 944 // Not a signal for ibus-memconf. | |
| 945 return; | |
| 946 } | |
| 947 | |
| 948 const std::string empty_string; | |
| 949 if (old_name != empty_string || new_name == empty_string) { | |
| 950 // ibus-memconf died? | |
| 951 LOG(WARNING) << "Unexpected name owner change: name=" << name | |
| 952 << ", old_name=" << old_name << ", new_name=" << new_name; | |
| 953 // TODO(yusukes): it might be nice to set |ibus_config_| to NULL and call | |
| 954 // |OnConnectionChange| with false here to allow Chrome to | |
| 955 // recover all input method configurations when ibus-memconf is | |
| 956 // automatically restarted by ibus-daemon. Though ibus-memconf is pretty | |
| 957 // stable and unlikely crashes. | |
| 958 return; | |
| 959 } | |
| 960 | |
| 961 VLOG(1) << "IBus config daemon is started. Recovering ibus_config_"; | |
| 962 | |
| 963 // Try to recover |ibus_config_|. If the |ibus_config_| object is | |
| 964 // successfully created, |OnConnectionChange| will be called to | |
| 965 // notify Chrome that IBus is ready. | |
| 966 MaybeRestoreConnections(); | |
| 967 } | |
| 968 | |
| 969 // Handles "FocusIn" signal from chromeos_input_method_ui. | |
| 970 void FocusIn(IBusPanelService* panel, const gchar* input_context_path) { | |
| 971 if (!input_context_path) { | |
| 972 LOG(ERROR) << "NULL context passed"; | |
| 973 } else { | |
| 974 VLOG(1) << "FocusIn: " << input_context_path; | |
| 975 } | |
| 976 // Remember the current ic path. | |
| 977 input_context_path_ = Or(input_context_path, ""); | |
| 978 } | |
| 979 | |
| 980 // Handles "RegisterProperties" signal from chromeos_input_method_ui. | |
| 981 void RegisterProperties(IBusPanelService* panel, IBusPropList* prop_list) { | |
| 982 DoRegisterProperties(prop_list); | |
| 983 } | |
| 984 | |
| 985 // Handles "UpdateProperty" signal from chromeos_input_method_ui. | |
| 986 void UpdateProperty(IBusPanelService* panel, IBusProperty* ibus_prop) { | |
| 987 VLOG(1) << "UpdateProperty"; | |
| 988 DCHECK(ibus_prop); | |
| 989 | |
| 990 // You can call | |
| 991 // LOG(INFO) << "\n" << PrintProp(ibus_prop, 0); | |
| 992 // here to dump |ibus_prop|. | |
| 993 | |
| 994 InputMethodPropertyList prop_list; // our representation. | |
| 995 if (!FlattenProperty(ibus_prop, &prop_list)) { | |
| 996 // Don't update the UI on errors. | |
| 997 LOG(ERROR) << "Malformed properties are detected"; | |
| 998 return; | |
| 999 } | |
| 1000 // Notify the change. | |
| 1001 if (!prop_list.empty()) { | |
| 1002 FOR_EACH_OBSERVER(Observer, observers_, | |
| 1003 OnUpdateImeProperty(prop_list)); | |
| 1004 } | |
| 1005 } | |
| 1006 | |
| 1007 // Removes input methods that are not whitelisted from | |
| 1008 // |requested_input_methods| and stores them on |out_filtered_input_methods|. | |
| 1009 // TODO(yusukes): Write unittest. | |
| 1010 void FilterInputMethods( | |
| 1011 const std::vector<std::string>& requested_input_methods, | |
| 1012 std::vector<std::string>* out_filtered_input_methods) { | |
| 1013 out_filtered_input_methods->clear(); | |
| 1014 for (size_t i = 0; i < requested_input_methods.size(); ++i) { | |
| 1015 const std::string& input_method = requested_input_methods[i]; | |
| 1016 if (whitelist_.InputMethodIdIsWhitelisted(input_method.c_str())) { | |
| 1017 if (!InputMethodUtil::IsKeyboardLayout(input_method)) { | |
| 1018 out_filtered_input_methods->push_back(input_method); | |
| 1019 } | |
| 1020 } else { | |
| 1021 LOG(ERROR) << "Unsupported input method: " << input_method; | |
| 1022 } | |
| 1023 } | |
| 1024 } | |
| 1025 | |
| 1026 // Frees input method names in |engines| and the list itself. Please make sure | |
| 1027 // that |engines| points the head of the list. | |
| 1028 void FreeInputMethodNames(GList* engines) { | |
| 1029 if (engines) { | |
| 1030 for (GList* cursor = engines; cursor; cursor = g_list_next(cursor)) { | |
| 1031 g_object_unref(IBUS_ENGINE_DESC(cursor->data)); | |
| 1032 } | |
| 1033 g_list_free(engines); | |
| 1034 } | |
| 1035 } | |
| 1036 | |
| 1037 // Copies input method names in |engines| to |out|. | |
| 1038 // TODO(yusukes): Write unittest. | |
| 1039 void AddInputMethodNames(const GList* engines, InputMethodDescriptors* out) { | |
| 1040 DCHECK(out); | |
| 1041 for (; engines; engines = g_list_next(engines)) { | |
| 1042 IBusEngineDesc* engine_desc = IBUS_ENGINE_DESC(engines->data); | |
| 1043 const gchar* name = ibus_engine_desc_get_name(engine_desc); | |
| 1044 const gchar* layout = ibus_engine_desc_get_layout(engine_desc); | |
| 1045 const gchar* language = ibus_engine_desc_get_language(engine_desc); | |
| 1046 if (whitelist_.InputMethodIdIsWhitelisted(name)) { | |
| 1047 out->push_back(CreateInputMethodDescriptor(name, "", layout, language)); | |
| 1048 VLOG(1) << name << " (preloaded)"; | |
| 1049 } | |
| 1050 } | |
| 1051 } | |
| 1052 | |
| 1053 // A callback function that will be called when ibus_config_set_value_async() | |
| 1054 // request is finished. | |
| 1055 static void SetInputMethodConfigCallback(GObject* source_object, | |
| 1056 GAsyncResult* res, | |
| 1057 gpointer user_data) { | |
| 1058 IBusConfig* config = IBUS_CONFIG(user_data); | |
| 1059 g_return_if_fail(config); | |
| 1060 | |
| 1061 GError* error = NULL; | |
| 1062 const gboolean result = | |
| 1063 ibus_config_set_value_async_finish(config, res, &error); | |
| 1064 | |
| 1065 if (!result) { | |
| 1066 std::string message = "(unknown error)"; | |
| 1067 if (error && error->message) { | |
| 1068 message = error->message; | |
| 1069 } | |
| 1070 LOG(ERROR) << "ibus_config_set_value_async failed: " << message; | |
| 1071 } | |
| 1072 | |
| 1073 if (error) { | |
| 1074 g_error_free(error); | |
| 1075 } | |
| 1076 g_object_unref(config); | |
| 1077 } | |
| 1078 | |
| 1079 // Connection to the ibus-daemon via IBus API. These objects are used to | |
| 1080 // call ibus-daemon's API (e.g. activate input methods, set config, ...) | |
| 1081 IBusBus* ibus_; | |
| 1082 IBusConfig* ibus_config_; | |
| 1083 | |
| 1084 // Current input context path. | |
| 1085 std::string input_context_path_; | |
| 1086 | |
| 1087 ObserverList<Observer> observers_; | |
| 1088 | |
| 1089 // An object which knows all valid input method and layout IDs. | |
| 1090 InputMethodWhitelist whitelist_; | |
| 1091 | |
| 1092 DISALLOW_COPY_AND_ASSIGN(IBusControllerImpl); | |
| 1093 }; | |
| 1094 | |
| 1095 #endif // defined(HAVE_IBUS) | |
| 1096 | |
| 1097 // The stub implementation is used if IBus is not present. | |
| 1098 // | |
| 1099 // Note that this class is intentionally built even if HAVE_IBUS is | |
| 1100 // defined so that we can easily tell build breakage when we change the | |
| 1101 // IBusControllerImpl but forget to update the stub implementation. | |
| 1102 class IBusControllerStubImpl : public IBusController { | |
| 1103 public: | |
| 1104 IBusControllerStubImpl() { | |
| 1105 } | |
| 1106 | |
| 1107 virtual void Connect() { | |
| 1108 }; | |
| 1109 | |
| 1110 virtual void AddObserver(Observer* observer) { | |
| 1111 } | |
| 1112 | |
| 1113 virtual void RemoveObserver(Observer* observer) { | |
| 1114 } | |
| 1115 | |
| 1116 virtual bool StopInputMethodProcess() { | |
| 1117 return true; | |
| 1118 } | |
| 1119 | |
| 1120 virtual bool ChangeInputMethod(const std::string& name) { | |
| 1121 return true; | |
| 1122 } | |
| 1123 | |
| 1124 virtual void SetImePropertyActivated(const std::string& key, | |
| 1125 bool activated) { | |
| 1126 } | |
| 1127 | |
| 1128 virtual bool SetInputMethodConfig(const std::string& section, | |
| 1129 const std::string& config_name, | |
| 1130 const InputMethodConfigValue& value) { | |
| 1131 return true; | |
| 1132 } | |
| 1133 | |
| 1134 virtual void SendHandwritingStroke(const HandwritingStroke& stroke) { | |
| 1135 } | |
| 1136 | |
| 1137 virtual void CancelHandwriting(int n_strokes) { | |
| 1138 } | |
| 1139 | |
| 1140 // This is for ibus_controller_unittest.cc. Since the test is usually compiled | |
| 1141 // without HAVE_IBUS, we have to provide the same implementation as | |
| 1142 // IBusControllerImpl to test the whitelist class. | |
| 1143 virtual bool InputMethodIdIsWhitelisted(const std::string& input_method_id) { | |
| 1144 return whitelist_.InputMethodIdIsWhitelisted(input_method_id); | |
| 1145 } | |
| 1146 // See the comment above. We have to keep the implementation the same as | |
| 1147 // IBusControllerImpl. | |
| 1148 virtual bool XkbLayoutIsSupported(const std::string& xkb_layout) { | |
| 1149 return whitelist_.XkbLayoutIsSupported(xkb_layout); | |
| 1150 } | |
| 1151 // See the comment above. We have to keep the implementation the same as | |
| 1152 // IBusControllerImpl. | |
| 1153 virtual InputMethodDescriptor CreateInputMethodDescriptor( | |
| 1154 const std::string& id, | |
| 1155 const std::string& name, | |
| 1156 const std::string& raw_layout, | |
| 1157 const std::string& language_code) { | |
| 1158 return InputMethodDescriptor(whitelist_, id, name, raw_layout, | |
| 1159 language_code); | |
| 1160 } | |
| 1161 | |
| 1162 private: | |
| 1163 InputMethodWhitelist whitelist_; | |
| 1164 | |
| 1165 DISALLOW_COPY_AND_ASSIGN(IBusControllerStubImpl); | |
| 1166 }; | |
| 1167 | |
| 1168 IBusController* IBusController::Create() { | 20 IBusController* IBusController::Create() { |
| 1169 #if defined(HAVE_IBUS) | 21 #if defined(HAVE_IBUS) |
| 1170 return new IBusControllerImpl; | 22 return new IBusControllerImpl; |
| 1171 #else | 23 #else |
| 1172 return new IBusControllerStubImpl; | 24 return new MockIBusController; |
| 1173 #endif | 25 #endif |
| 1174 } | 26 } |
| 1175 | 27 |
| 1176 IBusController::~IBusController() { | |
| 1177 } | |
| 1178 | |
| 1179 } // namespace input_method | 28 } // namespace input_method |
| 1180 } // namespace chromeos | 29 } // namespace chromeos |
| OLD | NEW |