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

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

Issue 9999018: chrome/browser/chromeos/input_method/ refactoring [part 6 of 6] (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: rebase, remove |should_hide_properties_| Created 8 years, 8 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
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, &current_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
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698