Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(21)

Side by Side Diff: chrome/browser/chromeos/input_method/ibus_controller.cc

Issue 7046069: Copy chromeos_input_method* from libcros as-is. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Created 9 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(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
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698