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