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 |