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

Side by Side Diff: chrome/browser/chromeos/input_method/ibus_controller_impl.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 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
(Empty)
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
3 // found in the LICENSE file.
4
5 #include "chrome/browser/chromeos/input_method/ibus_controller_impl.h"
6
7 #include <algorithm> // for std::reverse.
8 #include <cstdio>
9 #include <cstring> // for std::strcmp.
10 #include <set>
11 #include <sstream>
12 #include <stack>
13 #include <utility>
14
15 #include "base/memory/scoped_ptr.h"
16 #include "base/stringprintf.h"
17 #include "base/string_split.h"
18 #include "chrome/browser/chromeos/input_method/input_method_config.h"
19 #include "chrome/browser/chromeos/input_method/input_method_property.h"
20 #include "chrome/browser/chromeos/input_method/input_method_util.h"
21
22 // TODO(nona): Remove libibus dependency from this file. Then, write unit tests
23 // for all functions in this file. crbug.com/26334
24 #if defined(HAVE_IBUS)
25 #include <ibus.h>
26 #endif
27
28 namespace {
29
30 // Finds a property which has |new_prop.key| from |prop_list|, and replaces the
31 // property with |new_prop|. Returns true if such a property is found.
32 bool FindAndUpdateProperty(
33 const chromeos::input_method::InputMethodProperty& new_prop,
34 chromeos::input_method::InputMethodPropertyList* prop_list) {
35 for (size_t i = 0; i < prop_list->size(); ++i) {
36 chromeos::input_method::InputMethodProperty& prop = prop_list->at(i);
37 if (prop.key == new_prop.key) {
38 const int saved_id = prop.selection_item_id;
39 // Update the list except the radio id. As written in
40 // chromeos_input_method.h, |prop.selection_item_id| is dummy.
41 prop = new_prop;
42 prop.selection_item_id = saved_id;
43 return true;
44 }
45 }
46 return false;
47 }
48
49 } // namespace
50
51 namespace chromeos {
52 namespace input_method {
53
54 #if defined(HAVE_IBUS)
55 const char kPanelObjectKey[] = "panel-object";
56
57 namespace {
58
59 const char* Or(const char* str1, const char* str2) {
60 return str1 ? str1 : str2;
61 }
62
63 // Returns true if |key| is blacklisted.
64 bool PropertyKeyIsBlacklisted(const char* key) {
65 // The list of input method property keys that we don't handle.
66 static const char* kInputMethodPropertyKeysBlacklist[] = {
67 "setup", // used in ibus-m17n.
68 "status", // used in ibus-m17n.
69 };
70 for (size_t i = 0; i < arraysize(kInputMethodPropertyKeysBlacklist); ++i) {
71 if (!std::strcmp(key, kInputMethodPropertyKeysBlacklist[i]))
72 return true;
73 }
74 return false;
75 }
76
77 // Returns IBusInputContext for |input_context_path|. NULL on errors.
78 IBusInputContext* GetInputContext(const std::string& input_context_path,
79 IBusBus* ibus) {
80 GDBusConnection* connection = ibus_bus_get_connection(ibus);
81 if (!connection) {
82 LOG(ERROR) << "IBusConnection is null";
83 return NULL;
84 }
85 // This function does not issue an IBus IPC.
86 IBusInputContext* context = ibus_input_context_get_input_context(
87 input_context_path.c_str(), connection);
88 if (!context)
89 LOG(ERROR) << "IBusInputContext is null: " << input_context_path;
90 return context;
91 }
92
93 // Returns true if |prop| has children.
94 bool PropertyHasChildren(IBusProperty* prop) {
95 return prop && ibus_property_get_sub_props(prop) &&
96 ibus_prop_list_get(ibus_property_get_sub_props(prop), 0);
97 }
98
99 // This function is called by and FlattenProperty() and converts IBus
100 // representation of a property, |ibus_prop|, to our own and push_back the
101 // result to |out_prop_list|. This function returns true on success, and
102 // returns false if sanity checks for |ibus_prop| fail.
103 bool ConvertProperty(IBusProperty* ibus_prop,
104 int selection_item_id,
105 InputMethodPropertyList* out_prop_list) {
106 DCHECK(ibus_prop);
107 DCHECK(out_prop_list);
108
109 const IBusPropType type = ibus_property_get_prop_type(ibus_prop);
110 const IBusPropState state = ibus_property_get_state(ibus_prop);
111 const IBusText* tooltip = ibus_property_get_tooltip(ibus_prop);
112 const IBusText* label = ibus_property_get_label(ibus_prop);
113 const gchar* key = ibus_property_get_key(ibus_prop);
114 DCHECK(key);
115
116 // Sanity checks.
117 const bool has_sub_props = PropertyHasChildren(ibus_prop);
118 if (has_sub_props && (type != PROP_TYPE_MENU)) {
119 LOG(ERROR) << "The property has sub properties, "
120 << "but the type of the property is not PROP_TYPE_MENU";
121 return false;
122 }
123 if ((!has_sub_props) && (type == PROP_TYPE_MENU)) {
124 // This is usually not an error. ibus-daemon sometimes sends empty props.
125 VLOG(1) << "Property list is empty";
126 return false;
127 }
128 if (type == PROP_TYPE_SEPARATOR || type == PROP_TYPE_MENU) {
129 // This is not an error, but we don't push an item for these types.
130 return true;
131 }
132
133 const bool is_selection_item = (type == PROP_TYPE_RADIO);
134 selection_item_id = is_selection_item ?
135 selection_item_id : InputMethodProperty::kInvalidSelectionItemId;
136
137 bool is_selection_item_checked = false;
138 if (state == PROP_STATE_INCONSISTENT) {
139 LOG(WARNING) << "The property is in PROP_STATE_INCONSISTENT, "
140 << "which is not supported.";
141 } else if ((!is_selection_item) && (state == PROP_STATE_CHECKED)) {
142 LOG(WARNING) << "PROP_STATE_CHECKED is meaningful only if the type is "
143 << "PROP_TYPE_RADIO.";
144 } else {
145 is_selection_item_checked = (state == PROP_STATE_CHECKED);
146 }
147
148 if (!key)
149 LOG(ERROR) << "key is NULL";
150 if (tooltip && !tooltip->text) {
151 LOG(ERROR) << "tooltip is NOT NULL, but tooltip->text IS NULL: key="
152 << Or(key, "");
153 }
154 if (label && !label->text) {
155 LOG(ERROR) << "label is NOT NULL, but label->text IS NULL: key="
156 << Or(key, "");
157 }
158
159 // This label will be localized later.
160 // See chrome/browser/chromeos/input_method/input_method_util.cc.
161 std::string label_to_use = (tooltip && tooltip->text) ? tooltip->text : "";
162 if (label_to_use.empty()) {
163 // Usually tooltips are more descriptive than labels.
164 label_to_use = (label && label->text) ? label->text : "";
165 }
166 if (label_to_use.empty()) {
167 LOG(ERROR) << "The tooltip and label are both empty. Use " << key;
168 label_to_use = Or(key, "");
169 }
170
171 out_prop_list->push_back(InputMethodProperty(key,
172 label_to_use,
173 is_selection_item,
174 is_selection_item_checked,
175 selection_item_id));
176 return true;
177 }
178
179 // Converts |ibus_prop| to |out_prop_list|. Please note that |ibus_prop|
180 // may or may not have children. See the comment for FlattenPropertyList
181 // for details. Returns true if no error is found.
182 bool FlattenProperty(IBusProperty* ibus_prop,
183 InputMethodPropertyList* out_prop_list) {
184 DCHECK(ibus_prop);
185 DCHECK(out_prop_list);
186
187 int selection_item_id = -1;
188 std::stack<std::pair<IBusProperty*, int> > prop_stack;
189 prop_stack.push(std::make_pair(ibus_prop, selection_item_id));
190
191 while (!prop_stack.empty()) {
192 IBusProperty* prop = prop_stack.top().first;
193 const gchar* key = ibus_property_get_key(prop);
194 const int current_selection_item_id = prop_stack.top().second;
195 prop_stack.pop();
196
197 // Filter out unnecessary properties.
198 if (PropertyKeyIsBlacklisted(key))
199 continue;
200
201 // Convert |prop| to InputMethodProperty and push it to |out_prop_list|.
202 if (!ConvertProperty(prop, current_selection_item_id, out_prop_list))
203 return false;
204
205 // Process childrens iteratively (if any). Push all sub properties to the
206 // stack.
207 if (PropertyHasChildren(prop)) {
208 ++selection_item_id;
209 for (int i = 0;; ++i) {
210 IBusProperty* sub_prop =
211 ibus_prop_list_get(ibus_property_get_sub_props(prop), i);
212 if (!sub_prop)
213 break;
214 prop_stack.push(std::make_pair(sub_prop, selection_item_id));
215 }
216 ++selection_item_id;
217 }
218 }
219 std::reverse(out_prop_list->begin(), out_prop_list->end());
220
221 return true;
222 }
223
224 // Converts IBus representation of a property list, |ibus_prop_list| to our
225 // own. This function also flatten the original list (actually it's a tree).
226 // Returns true if no error is found. The conversion to our own type is
227 // necessary since our language switcher in Chrome tree don't (or can't) know
228 // IBus types. Here is an example:
229 //
230 // ======================================================================
231 // Input:
232 //
233 // --- Item-1
234 // |- Item-2
235 // |- SubMenuRoot --- Item-3-1
236 // | |- Item-3-2
237 // | |- Item-3-3
238 // |- Item-4
239 //
240 // (Note: Item-3-X is a selection item since they're on a sub menu.)
241 //
242 // Output:
243 //
244 // Item-1, Item-2, Item-3-1, Item-3-2, Item-3-3, Item-4
245 // (Note: SubMenuRoot does not appear in the output.)
246 // ======================================================================
247 bool FlattenPropertyList(IBusPropList* ibus_prop_list,
248 InputMethodPropertyList* out_prop_list) {
249 DCHECK(ibus_prop_list);
250 DCHECK(out_prop_list);
251
252 IBusProperty* fake_root_prop = ibus_property_new("Dummy.Key",
253 PROP_TYPE_MENU,
254 NULL, /* label */
255 "", /* icon */
256 NULL, /* tooltip */
257 FALSE, /* sensitive */
258 FALSE, /* visible */
259 PROP_STATE_UNCHECKED,
260 ibus_prop_list);
261 g_return_val_if_fail(fake_root_prop, false);
262 // Increase the ref count so it won't get deleted when |fake_root_prop|
263 // is deleted.
264 g_object_ref(ibus_prop_list);
265 const bool result = FlattenProperty(fake_root_prop, out_prop_list);
266 g_object_unref(fake_root_prop);
267
268 return result;
269 }
270
271 // Debug print function.
272 const char* PropTypeToString(int prop_type) {
273 switch (static_cast<IBusPropType>(prop_type)) {
274 case PROP_TYPE_NORMAL:
275 return "NORMAL";
276 case PROP_TYPE_TOGGLE:
277 return "TOGGLE";
278 case PROP_TYPE_RADIO:
279 return "RADIO";
280 case PROP_TYPE_MENU:
281 return "MENU";
282 case PROP_TYPE_SEPARATOR:
283 return "SEPARATOR";
284 }
285 return "UNKNOWN";
286 }
287
288 // Debug print function.
289 const char* PropStateToString(int prop_state) {
290 switch (static_cast<IBusPropState>(prop_state)) {
291 case PROP_STATE_UNCHECKED:
292 return "UNCHECKED";
293 case PROP_STATE_CHECKED:
294 return "CHECKED";
295 case PROP_STATE_INCONSISTENT:
296 return "INCONSISTENT";
297 }
298 return "UNKNOWN";
299 }
300
301 // Debug print function.
302 std::string Spacer(int n) {
303 return std::string(n, ' ');
304 }
305
306 // Debug print function.
307 std::string PrintProp(IBusProperty *prop, int tree_level) {
308 std::string PrintPropList(IBusPropList *prop_list, int tree_level);
309
310 if (!prop)
311 return "";
312
313 const IBusPropType type = ibus_property_get_prop_type(prop);
314 const IBusPropState state = ibus_property_get_state(prop);
315 const IBusText* tooltip = ibus_property_get_tooltip(prop);
316 const IBusText* label = ibus_property_get_label(prop);
317 const gchar* key = ibus_property_get_key(prop);
318
319 std::stringstream stream;
320 stream << Spacer(tree_level) << "=========================" << std::endl;
321 stream << Spacer(tree_level) << "key: " << Or(key, "<none>")
322 << std::endl;
323 stream << Spacer(tree_level) << "label: "
324 << ((label && label->text) ? label->text : "<none>")
325 << std::endl;
326 stream << Spacer(tree_level) << "tooptip: "
327 << ((tooltip && tooltip->text)
328 ? tooltip->text : "<none>") << std::endl;
329 stream << Spacer(tree_level) << "sensitive: "
330 << (ibus_property_get_sensitive(prop) ? "YES" : "NO") << std::endl;
331 stream << Spacer(tree_level) << "visible: "
332 << (ibus_property_get_visible(prop) ? "YES" : "NO") << std::endl;
333 stream << Spacer(tree_level) << "type: " << PropTypeToString(type)
334 << std::endl;
335 stream << Spacer(tree_level) << "state: " << PropStateToString(state)
336 << std::endl;
337 stream << Spacer(tree_level) << "sub_props: "
338 << (PropertyHasChildren(prop) ? "" : "<none>") << std::endl;
339 stream << PrintPropList(ibus_property_get_sub_props(prop), tree_level + 1);
340 stream << Spacer(tree_level) << "=========================" << std::endl;
341
342 return stream.str();
343 }
344
345 // Debug print function.
346 std::string PrintPropList(IBusPropList *prop_list, int tree_level) {
347 if (!prop_list)
348 return "";
349
350 std::stringstream stream;
351 for (int i = 0;; ++i) {
352 IBusProperty* prop = ibus_prop_list_get(prop_list, i);
353 if (!prop)
354 break;
355 stream << PrintProp(prop, tree_level);
356 }
357 return stream.str();
358 }
359
360 } // namespace
361
362 IBusControllerImpl::IBusControllerImpl()
363 : ibus_(NULL),
364 ibus_config_(NULL),
365 should_launch_daemon_(false),
366 process_handle_(base::kNullProcessHandle) {
367 }
368
369 IBusControllerImpl::~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(BusConnectedThunk)),
376 this);
377 g_signal_handlers_disconnect_by_func(
378 ibus_,
379 reinterpret_cast<gpointer>(G_CALLBACK(BusDisconnectedThunk)),
380 this);
381 g_signal_handlers_disconnect_by_func(
382 ibus_,
383 reinterpret_cast<gpointer>(G_CALLBACK(BusNameOwnerChangedThunk)),
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 bool IBusControllerImpl::Start() {
407 MaybeInitializeIBusBus();
408 should_launch_daemon_ = true;
409 if (IBusConnectionsAreAlive())
410 return true;
411 return MaybeLaunchIBusDaemon();
412 }
413
414 bool IBusControllerImpl::Stop() {
415 if (IBusConnectionsAreAlive()) {
416 // Ask IBus to exit *asynchronously*.
417 ibus_bus_exit_async(ibus_,
418 FALSE /* do not restart */,
419 -1 /* timeout */,
420 NULL /* cancellable */,
421 NULL /* callback */,
422 NULL /* user_data */);
423 if (ibus_config_) {
424 // Release |ibus_config_| unconditionally to make sure next
425 // IBusConnectionsAreAlive() call will return false.
426 g_object_unref(ibus_config_);
427 ibus_config_ = NULL;
428 }
429 } else if (process_handle_ != base::kNullProcessHandle) {
430 base::KillProcess(process_handle_, -1, false /* wait */);
431 LOG(ERROR) << "Killing ibus-daemon. PID="
432 << base::GetProcId(process_handle_);
433 } else {
434 // The daemon hasn't been started yet.
435 }
436
437 process_handle_ = base::kNullProcessHandle;
438 should_launch_daemon_ = false;
439 return true;
440 }
441
442 bool IBusControllerImpl::ChangeInputMethod(const std::string& id) {
443 DCHECK(should_launch_daemon_);
444
445 // Sanity checks.
446 DCHECK(!InputMethodUtil::IsKeyboardLayout(id));
447 if (!whitelist_.InputMethodIdIsWhitelisted(id) &&
448 !InputMethodUtil::IsExtensionInputMethod(id))
449 return false;
450
451 // Clear input method properties unconditionally if |id| is not equal to
452 // |current_input_method_id_|.
453 //
454 // When switching to another input method and no text area is focused,
455 // RegisterProperties signal for the new input method will NOT be sent
456 // until a text area is focused. Therefore, we have to clear the old input
457 // method properties here to keep the input method switcher status
458 // consistent.
459 //
460 // When |id| and |current_input_method_id_| are the same, the properties
461 // shouldn't be cleared. If we do that, something wrong happens in step #4
462 // below:
463 // 1. Enable "xkb:us::eng" and "mozc". Switch to "mozc".
464 // 2. Focus Omnibox. IME properties for mozc are sent to Chrome.
465 // 3. Switch to "xkb:us::eng". No function in this file is called.
466 // 4. Switch back to "mozc". ChangeInputMethod("mozc") is called, but it's
467 // basically NOP since ibus-daemon's current IME is already "mozc".
468 // IME properties are not sent to Chrome for the same reason.
469 if (id != current_input_method_id_)
470 RegisterProperties(NULL, NULL);
471
472 current_input_method_id_ = id;
473
474 if (!IBusConnectionsAreAlive()) {
475 LOG(INFO) << "ChangeInputMethod: IBus connection is not alive (yet).";
476 // |id| will become usable shortly since Start() has already been called.
477 // Just return true.
478 } else {
479 SendChangeInputMethodRequest(id);
480 }
481
482 return true;
483 }
484
485 bool IBusControllerImpl::ActivateInputMethodProperty(const std::string& key) {
486 if (!IBusConnectionsAreAlive()) {
487 LOG(ERROR) << "ActivateInputMethodProperty: IBus connection is not alive";
488 return false;
489 }
490 if (current_input_context_path_.empty()) {
491 LOG(ERROR) << "Input context is unknown";
492 return false;
493 }
494
495 // The third parameter of ibus_input_context_property_activate() has to be
496 // true when the |key| points to a radio button. false otherwise.
497 bool is_radio = true;
498 size_t i;
499 for (i = 0; i < current_property_list_.size(); ++i) {
500 if (current_property_list_[i].key == key) {
501 is_radio = current_property_list_[i].is_selection_item;
502 break;
503 }
504 }
505 if (i == current_property_list_.size()) {
506 LOG(ERROR) << "ActivateInputMethodProperty: unknown key: " << key;
507 return false;
508 }
509
510 IBusInputContext* context =
511 GetInputContext(current_input_context_path_, ibus_);
512 if (!context)
513 return false;
514
515 // Activate the property *asynchronously*.
516 ibus_input_context_property_activate(context, key.c_str(), is_radio);
517
518 // We don't have to call ibus_proxy_destroy(context) explicitly here,
519 // i.e. we can just call g_object_unref(context), since g_object_unref can
520 // trigger both dispose, which is overridden by src/ibusproxy.c, and
521 // finalize functions. For details, see
522 // http://library.gnome.org/devel/gobject/stable/gobject-memory.html
523 g_object_unref(context);
524
525 return true;
526 }
527
528 #if defined(USE_VIRTUAL_KEYBOARD)
529 // IBusController override.
530 void IBusControllerImpl::SendHandwritingStroke(
531 const HandwritingStroke& stroke) {
532 if (stroke.size() < 2) {
533 LOG(WARNING) << "Empty stroke data or a single dot is passed.";
534 return;
535 }
536
537 IBusInputContext* context =
538 GetInputContext(current_input_context_path_, ibus_);
539 if (!context)
540 return;
541
542 const size_t raw_stroke_size = stroke.size() * 2;
543 scoped_array<double> raw_stroke(new double[raw_stroke_size]);
544 for (size_t n = 0; n < stroke.size(); ++n) {
545 raw_stroke[n * 2] = stroke[n].first; // x
546 raw_stroke[n * 2 + 1] = stroke[n].second; // y
547 }
548 ibus_input_context_process_hand_writing_event(
549 context, raw_stroke.get(), raw_stroke_size);
550 g_object_unref(context);
551 }
552
553 // IBusController override.
554 void IBusControllerImpl::CancelHandwriting(int n_strokes) {
555 IBusInputContext* context =
556 GetInputContext(current_input_context_path_, ibus_);
557 if (!context)
558 return;
559 ibus_input_context_cancel_hand_writing(context, n_strokes);
560 g_object_unref(context);
561 }
562 #endif
563
564 bool IBusControllerImpl::IBusConnectionsAreAlive() {
565 return (process_handle_ != base::kNullProcessHandle) &&
566 ibus_ && ibus_bus_is_connected(ibus_) && ibus_config_;
567 }
568
569 void IBusControllerImpl::MaybeRestoreConnections() {
570 if (IBusConnectionsAreAlive())
571 return;
572 MaybeRestoreIBusConfig();
573 if (IBusConnectionsAreAlive()) {
574 LOG(INFO) << "ibus-daemon and ibus-memconf processes are ready.";
575 ConnectPanelServiceSignals();
576 SendAllInputMethodConfigs();
577 if (!current_input_method_id_.empty())
578 SendChangeInputMethodRequest(current_input_method_id_);
579 }
580 }
581
582 void IBusControllerImpl::MaybeInitializeIBusBus() {
583 if (ibus_)
584 return;
585
586 ibus_init();
587 // Establish IBus connection between ibus-daemon to change the current input
588 // method engine, properties, and so on.
589 ibus_ = ibus_bus_new();
590 DCHECK(ibus_);
591
592 // Register callback functions for IBusBus signals.
593 ConnectBusSignals();
594
595 // Ask libibus to watch the NameOwnerChanged signal *asynchronously*.
596 ibus_bus_set_watch_dbus_signal(ibus_, TRUE);
597
598 if (ibus_bus_is_connected(ibus_)) {
599 LOG(ERROR) << "IBus connection is ready: ibus-daemon is already running?";
600 BusConnected(ibus_);
601 }
602 }
603
604 void IBusControllerImpl::MaybeRestoreIBusConfig() {
605 if (!ibus_)
606 return;
607
608 // Destroy the current |ibus_config_| object. No-op if it's NULL.
609 MaybeDestroyIBusConfig();
610
611 if (ibus_config_)
612 return;
613
614 GDBusConnection* ibus_connection = ibus_bus_get_connection(ibus_);
615 if (!ibus_connection) {
616 VLOG(1) << "Couldn't create an ibus config object since "
617 << "IBus connection is not ready.";
618 return;
619 }
620
621 const gboolean disconnected
622 = g_dbus_connection_is_closed(ibus_connection);
623 if (disconnected) {
624 // |ibus_| object is not NULL, but the connection between ibus-daemon
625 // is not yet established. In this case, we don't create |ibus_config_|
626 // object.
627 LOG(ERROR) << "Couldn't create an ibus config object since "
628 << "IBus connection is closed.";
629 return;
630 }
631 // If memconf is not successfully started yet, ibus_config_new() will
632 // return NULL. Otherwise, it returns a transfer-none and non-floating
633 // object. ibus_config_new() sometimes issues a D-Bus *synchronous* IPC
634 // to check if the org.freedesktop.IBus.Config service is available.
635 ibus_config_ = ibus_config_new(ibus_connection,
636 NULL /* do not cancel the operation */,
637 NULL /* do not get error information */);
638 if (!ibus_config_) {
639 LOG(ERROR) << "ibus_config_new() failed. ibus-memconf is not ready?";
640 return;
641 }
642
643 // TODO(yusukes): g_object_weak_ref might be better since it allows
644 // libcros to detect the delivery of the "destroy" glib signal the
645 // |ibus_config_| object.
646 g_object_ref(ibus_config_);
647 VLOG(1) << "ibus_config_ is ready.";
648 }
649
650 void IBusControllerImpl::MaybeDestroyIBusConfig() {
651 if (!ibus_) {
652 LOG(ERROR) << "MaybeDestroyIBusConfig: ibus_ is NULL";
653 return;
654 }
655 if (ibus_config_ && !ibus_bus_is_connected(ibus_)) {
656 g_object_unref(ibus_config_);
657 ibus_config_ = NULL;
658 }
659 }
660
661 void IBusControllerImpl::SendChangeInputMethodRequest(const std::string& id) {
662 // Change the global engine *asynchronously*.
663 ibus_bus_set_global_engine_async(ibus_,
664 id.c_str(),
665 -1, // use the default ibus timeout
666 NULL, // cancellable
667 NULL, // callback
668 NULL); // user_data
669 }
670
671 void IBusControllerImpl::SendAllInputMethodConfigs() {
672 DCHECK(IBusConnectionsAreAlive());
673
674 InputMethodConfigRequests::const_iterator iter =
675 current_config_values_.begin();
676 for (; iter != current_config_values_.end(); ++iter) {
677 SetInputMethodConfigInternal(iter->first, iter->second);
678 }
679 }
680
681 bool IBusControllerImpl::SetInputMethodConfigInternal(
682 const ConfigKeyType& key,
683 const InputMethodConfigValue& value) {
684 if (!IBusConnectionsAreAlive())
685 return true;
686
687 // Convert the type of |value| from our structure to GVariant.
688 GVariant* variant = NULL;
689 switch (value.type) {
690 case InputMethodConfigValue::kValueTypeString:
691 variant = g_variant_new_string(value.string_value.c_str());
692 break;
693 case InputMethodConfigValue::kValueTypeInt:
694 variant = g_variant_new_int32(value.int_value);
695 break;
696 case InputMethodConfigValue::kValueTypeBool:
697 variant = g_variant_new_boolean(value.bool_value);
698 break;
699 case InputMethodConfigValue::kValueTypeStringList:
700 GVariantBuilder variant_builder;
701 g_variant_builder_init(&variant_builder, G_VARIANT_TYPE("as"));
702 const size_t size = value.string_list_value.size();
703 // |size| could be 0 for some special configurations such as IBus hotkeys.
704 for (size_t i = 0; i < size; ++i) {
705 g_variant_builder_add(&variant_builder,
706 "s",
707 value.string_list_value[i].c_str());
708 }
709 variant = g_variant_builder_end(&variant_builder);
710 break;
711 }
712
713 if (!variant) {
714 LOG(ERROR) << "SendInputMethodConfig: unknown value.type";
715 return false;
716 }
717 DCHECK(g_variant_is_floating(variant));
718 DCHECK(ibus_config_);
719
720 // Set an ibus configuration value *asynchronously*.
721 ibus_config_set_value_async(ibus_config_,
722 key.first.c_str(),
723 key.second.c_str(),
724 variant,
725 -1, // use the default ibus timeout
726 NULL, // cancellable
727 SetInputMethodConfigCallback,
728 g_object_ref(ibus_config_));
729
730 // Since |variant| is floating, ibus_config_set_value_async consumes
731 // (takes ownership of) the variable.
732 return true;
733 }
734
735 void IBusControllerImpl::ConnectBusSignals() {
736 if (!ibus_)
737 return;
738
739 // We use g_signal_connect_after here since the callback should be called
740 // *after* the IBusBusDisconnectedCallback in chromeos_input_method_ui.cc
741 // is called. chromeos_input_method_ui.cc attaches the panel service object
742 // to |ibus_|, and the callback in this file use the attached object.
743 g_signal_connect_after(ibus_,
744 "connected",
745 G_CALLBACK(BusConnectedThunk),
746 this);
747
748 g_signal_connect(ibus_,
749 "disconnected",
750 G_CALLBACK(BusDisconnectedThunk),
751 this);
752
753 g_signal_connect(ibus_,
754 "name-owner-changed",
755 G_CALLBACK(BusNameOwnerChangedThunk),
756 this);
757 }
758
759 void IBusControllerImpl::ConnectPanelServiceSignals() {
760 if (!ibus_)
761 return;
762
763 IBusPanelService* ibus_panel_service = IBUS_PANEL_SERVICE(
764 g_object_get_data(G_OBJECT(ibus_), kPanelObjectKey));
765 if (!ibus_panel_service) {
766 LOG(ERROR) << "IBusPanelService is NOT available.";
767 return;
768 }
769 // We don't _ref() or _weak_ref() the panel service object, since we're not
770 // interested in the life time of the object.
771
772 g_signal_connect(ibus_panel_service,
773 "focus-in",
774 G_CALLBACK(FocusInThunk),
775 this);
776 g_signal_connect(ibus_panel_service,
777 "register-properties",
778 G_CALLBACK(RegisterPropertiesThunk),
779 this);
780 g_signal_connect(ibus_panel_service,
781 "update-property",
782 G_CALLBACK(UpdatePropertyThunk),
783 this);
784 }
785
786 void IBusControllerImpl::BusConnected(IBusBus* bus) {
787 LOG(INFO) << "IBus connection is established.";
788 MaybeRestoreConnections();
789 }
790
791 void IBusControllerImpl::BusDisconnected(IBusBus* bus) {
792 LOG(INFO) << "IBus connection is terminated.";
793 // ibus-daemon might be terminated. Since |ibus_| object will automatically
794 // connect to the daemon if it restarts, we don't have to set NULL on ibus_.
795 // Call MaybeDestroyIBusConfig() to set |ibus_config_| to NULL temporarily.
796 MaybeDestroyIBusConfig();
797 }
798
799 void IBusControllerImpl::BusNameOwnerChanged(IBusBus* bus,
800 const gchar* name,
801 const gchar* old_name,
802 const gchar* new_name) {
803 DCHECK(name);
804 DCHECK(old_name);
805 DCHECK(new_name);
806
807 if (name != std::string("org.freedesktop.IBus.Config")) {
808 // Not a signal for ibus-memconf.
809 return;
810 }
811
812 const std::string empty_string;
813 if (old_name != empty_string || new_name == empty_string) {
814 // ibus-memconf died?
815 LOG(WARNING) << "Unexpected name owner change: name=" << name
816 << ", old_name=" << old_name << ", new_name=" << new_name;
817 // TODO(yusukes): it might be nice to set |ibus_config_| to NULL and call
818 // a new callback function like OnDisconnect() here to allow Chrome to
819 // recover all input method configurations when ibus-memconf is
820 // automatically restarted by ibus-daemon. Though ibus-memconf is pretty
821 // stable and unlikely crashes.
822 return;
823 }
824 VLOG(1) << "IBus config daemon is started. Recovering ibus_config_";
825
826 // Try to recover |ibus_config_|. If the |ibus_config_| object is
827 // successfully created, |OnConnectionChange| will be called to
828 // notify Chrome that IBus is ready.
829 MaybeRestoreConnections();
830 }
831
832 void IBusControllerImpl::FocusIn(IBusPanelService* panel,
833 const gchar* input_context_path) {
834 if (!input_context_path)
835 LOG(ERROR) << "NULL context passed";
836 else
837 VLOG(1) << "FocusIn: " << input_context_path;
838 // Remember the current ic path.
839 current_input_context_path_ = Or(input_context_path, "");
840 }
841
842 void IBusControllerImpl::RegisterProperties(IBusPanelService* panel,
843 IBusPropList* ibus_prop_list) {
844 // Note: |panel| can be NULL. See ChangeInputMethod().
845 VLOG(1) << "RegisterProperties" << (ibus_prop_list ? "" : " (clear)");
846
847 current_property_list_.clear();
848 if (ibus_prop_list) {
849 // You can call
850 // LOG(INFO) << "\n" << PrintPropList(ibus_prop_list, 0);
851 // here to dump |ibus_prop_list|.
852 if (!FlattenPropertyList(ibus_prop_list, &current_property_list_)) {
853 // Clear properties on errors.
854 current_property_list_.clear();
855 }
856 }
857 FOR_EACH_OBSERVER(Observer, observers_, PropertyChanged());
858 }
859
860 void IBusControllerImpl::UpdateProperty(IBusPanelService* panel,
861 IBusProperty* ibus_prop) {
862 VLOG(1) << "UpdateProperty";
863 DCHECK(ibus_prop);
864
865 // You can call
866 // LOG(INFO) << "\n" << PrintProp(ibus_prop, 0);
867 // here to dump |ibus_prop|.
868
869 InputMethodPropertyList prop_list; // our representation.
870 if (!FlattenProperty(ibus_prop, &prop_list)) {
871 // Don't update the UI on errors.
872 LOG(ERROR) << "Malformed properties are detected";
873 return;
874 }
875
876 // Notify the change.
877 if (!prop_list.empty()) {
878 for (size_t i = 0; i < prop_list.size(); ++i) {
879 FindAndUpdateProperty(prop_list[i], &current_property_list_);
880 }
881 FOR_EACH_OBSERVER(Observer, observers_, PropertyChanged());
882 }
883 }
884
885 bool IBusControllerImpl::MaybeLaunchIBusDaemon() {
886 static const char kIBusDaemonPath[] = "/usr/bin/ibus-daemon";
887
888 if (process_handle_ != base::kNullProcessHandle) {
889 LOG(ERROR) << "MaybeLaunchIBusDaemon: ibus-daemon is already running.";
890 return false;
891 }
892 if (!should_launch_daemon_)
893 return false;
894
895 // TODO(zork): Send output to /var/log/ibus.log
896 const std::string ibus_daemon_command_line =
897 base::StringPrintf("%s --panel=disable --cache=none --restart --replace",
898 kIBusDaemonPath);
899 if (!LaunchProcess(ibus_daemon_command_line,
900 &process_handle_,
901 reinterpret_cast<GChildWatchFunc>(OnIBusDaemonExit))) {
902 LOG(ERROR) << "Failed to launch " << ibus_daemon_command_line;
903 return false;
904 }
905 return true;
906 }
907
908 bool IBusControllerImpl::LaunchProcess(const std::string& command_line,
909 base::ProcessHandle* process_handle,
910 GChildWatchFunc watch_func) {
911 std::vector<std::string> argv;
912 base::ProcessHandle handle = base::kNullProcessHandle;
913
914 base::SplitString(command_line, ' ', &argv);
915
916 if (!base::LaunchProcess(argv, base::LaunchOptions(), &handle)) {
917 LOG(ERROR) << "Could not launch: " << command_line;
918 return false;
919 }
920
921 // g_child_watch_add is necessary to prevent the process from becoming a
922 // zombie.
923 // TODO(yusukes): port g_child_watch_add to base/process_utils_posix.cc.
924 const base::ProcessId pid = base::GetProcId(handle);
925 g_child_watch_add(pid, watch_func, this);
926
927 *process_handle = handle;
928 VLOG(1) << command_line << "is started. PID=" << pid;
929 return true;
930 }
931
932 // static
933 void IBusControllerImpl::SetInputMethodConfigCallback(GObject* source_object,
934 GAsyncResult* res,
935 gpointer user_data) {
936 IBusConfig* config = IBUS_CONFIG(user_data);
937 g_return_if_fail(config);
938
939 GError* error = NULL;
940 const gboolean result =
941 ibus_config_set_value_async_finish(config, res, &error);
942
943 if (!result) {
944 std::string message = "(unknown error)";
945 if (error && error->message) {
946 message = error->message;
947 }
948 LOG(ERROR) << "ibus_config_set_value_async failed: " << message;
949 }
950
951 if (error)
952 g_error_free(error);
953 g_object_unref(config);
954 }
955
956 // static
957 void IBusControllerImpl::OnIBusDaemonExit(GPid pid,
958 gint status,
959 IBusControllerImpl* controller) {
960 if (controller->process_handle_ != base::kNullProcessHandle &&
961 base::GetProcId(controller->process_handle_) == pid) {
962 controller->process_handle_ = base::kNullProcessHandle;
963 }
964 // Restart the daemon if needed.
965 controller->MaybeLaunchIBusDaemon();
966 }
967 #endif // defined(HAVE_IBUS)
968
969 // static
970 bool IBusControllerImpl::FindAndUpdatePropertyForTesting(
971 const chromeos::input_method::InputMethodProperty& new_prop,
972 chromeos::input_method::InputMethodPropertyList* prop_list) {
973 return FindAndUpdateProperty(new_prop, prop_list);
974 }
975
976 } // namespace input_method
977 } // namespace chromeos
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698