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

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) {
Yusuke Sato 2012/04/12 08:56:50 unchaged
60 return str1 ? str1 : str2;
61 }
62
63 // Returns true if |key| is blacklisted.
64 bool PropertyKeyIsBlacklisted(const char* key) {
Yusuke Sato 2012/04/12 08:56:50 unchanged
65 // The list of input method property keys that we don't handle.
66 static const char* kInputMethodPropertyKeysBlacklist[] = {
67 "status", // used in ibus-m17n.
68 };
69 for (size_t i = 0; i < arraysize(kInputMethodPropertyKeysBlacklist); ++i) {
70 if (!std::strcmp(key, kInputMethodPropertyKeysBlacklist[i]))
71 return true;
72 }
73 return false;
74 }
75
76 // Returns IBusInputContext for |input_context_path|. NULL on errors.
77 IBusInputContext* GetInputContext(const std::string& input_context_path,
Yusuke Sato 2012/04/12 08:56:50 unchanged
78 IBusBus* ibus) {
79 GDBusConnection* connection = ibus_bus_get_connection(ibus);
80 if (!connection) {
81 LOG(ERROR) << "IBusConnection is null";
82 return NULL;
83 }
84 // This function does not issue an IBus IPC.
85 IBusInputContext* context = ibus_input_context_get_input_context(
86 input_context_path.c_str(), connection);
87 if (!context)
88 LOG(ERROR) << "IBusInputContext is null: " << input_context_path;
89 return context;
90 }
91
92 // Returns true if |prop| has children.
93 bool PropertyHasChildren(IBusProperty* prop) {
Yusuke Sato 2012/04/12 08:56:50 removed const_cast<> which is unnecessary for ibus
94 return prop && ibus_property_get_sub_props(prop) &&
95 ibus_prop_list_get(ibus_property_get_sub_props(prop), 0);
96 }
97
98 // This function is called by and FlattenProperty() and converts IBus
99 // representation of a property, |ibus_prop|, to our own and push_back the
100 // result to |out_prop_list|. This function returns true on success, and
101 // returns false if sanity checks for |ibus_prop| fail.
102 bool ConvertProperty(IBusProperty* ibus_prop,
Yusuke Sato 2012/04/12 08:56:50 unchanged
103 int selection_item_id,
104 InputMethodPropertyList* out_prop_list) {
105 DCHECK(ibus_prop);
106 DCHECK(out_prop_list);
107
108 const IBusPropType type = ibus_property_get_prop_type(ibus_prop);
109 const IBusPropState state = ibus_property_get_state(ibus_prop);
110 const IBusText* tooltip = ibus_property_get_tooltip(ibus_prop);
111 const IBusText* label = ibus_property_get_label(ibus_prop);
112 const gchar* key = ibus_property_get_key(ibus_prop);
113 DCHECK(key);
114
115 // Sanity checks.
116 const bool has_sub_props = PropertyHasChildren(ibus_prop);
117 if (has_sub_props && (type != PROP_TYPE_MENU)) {
118 LOG(ERROR) << "The property has sub properties, "
119 << "but the type of the property is not PROP_TYPE_MENU";
120 return false;
121 }
122 if ((!has_sub_props) && (type == PROP_TYPE_MENU)) {
123 // This is usually not an error. ibus-daemon sometimes sends empty props.
124 VLOG(1) << "Property list is empty";
125 return false;
126 }
127 if (type == PROP_TYPE_SEPARATOR || type == PROP_TYPE_MENU) {
128 // This is not an error, but we don't push an item for these types.
129 return true;
130 }
131
132 const bool is_selection_item = (type == PROP_TYPE_RADIO);
133 selection_item_id = is_selection_item ?
134 selection_item_id : InputMethodProperty::kInvalidSelectionItemId;
135
136 bool is_selection_item_checked = false;
137 if (state == PROP_STATE_INCONSISTENT) {
138 LOG(WARNING) << "The property is in PROP_STATE_INCONSISTENT, "
139 << "which is not supported.";
140 } else if ((!is_selection_item) && (state == PROP_STATE_CHECKED)) {
141 LOG(WARNING) << "PROP_STATE_CHECKED is meaningful only if the type is "
142 << "PROP_TYPE_RADIO.";
143 } else {
144 is_selection_item_checked = (state == PROP_STATE_CHECKED);
145 }
146
147 if (!key)
148 LOG(ERROR) << "key is NULL";
149 if (tooltip && !tooltip->text) {
150 LOG(ERROR) << "tooltip is NOT NULL, but tooltip->text IS NULL: key="
151 << Or(key, "");
152 }
153 if (label && !label->text) {
154 LOG(ERROR) << "label is NOT NULL, but label->text IS NULL: key="
155 << Or(key, "");
156 }
157
158 // This label will be localized later.
159 // See chrome/browser/chromeos/input_method/input_method_util.cc.
160 std::string label_to_use = (tooltip && tooltip->text) ? tooltip->text : "";
161 if (label_to_use.empty()) {
162 // Usually tooltips are more descriptive than labels.
163 label_to_use = (label && label->text) ? label->text : "";
164 }
165 if (label_to_use.empty()) {
166 // ibus-pinyin has a property whose label and tooltip are empty. Fall back
167 // to the 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,
Yusuke Sato 2012/04/12 08:56:50 unchanged (other than const_cast<> removal)
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,
Yusuke Sato 2012/04/12 08:56:50 unchanged
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) {
Yusuke Sato 2012/04/12 08:56:50 unchanged
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) {
Yusuke Sato 2012/04/12 08:56:50 unchanged
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) {
Yusuke Sato 2012/04/12 08:56:50 unchanged
303 return std::string(n, ' ');
304 }
305
306 // Debug print function.
307 std::string PrintProp(IBusProperty *prop, int tree_level) {
Yusuke Sato 2012/04/12 08:56:50 unchanged (other than const_cast<> removal)
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) {
Yusuke Sato 2012/04/12 08:56:50 unchanged
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()
Yusuke Sato 2012/04/12 08:56:50 changed. added member variable initialization.
363 : ibus_(NULL),
364 ibus_config_(NULL),
365 should_launch_daemon_(false),
366 process_handle_(base::kNullProcessHandle) {
367 }
368
369 IBusControllerImpl::~IBusControllerImpl() {
Yusuke Sato 2012/04/12 08:56:50 unchanged
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(const std::vector<std::string>& ids) {
Yusuke Sato 2012/04/12 08:56:50 new function
407 MaybeInitializeIBusBus();
408 should_launch_daemon_ = true;
409 return MaybeLaunchIBusDaemon();
410 }
411
412 bool IBusControllerImpl::Stop() {
Yusuke Sato 2012/04/12 08:56:50 renamed but unchanged.
413 if (IBusConnectionsAreAlive()) {
414 // Ask IBus to exit *asynchronously*.
415 ibus_bus_exit_async(ibus_,
416 FALSE /* do not restart */,
417 -1 /* timeout */,
418 NULL /* cancellable */,
419 NULL /* callback */,
420 NULL /* user_data */);
421 if (ibus_config_) {
422 // Release |ibus_config_| unconditionally to make sure next
423 // IBusConnectionsAreAlive() call will return false.
424 g_object_unref(ibus_config_);
425 ibus_config_ = NULL;
426 }
427 } else if (process_handle_ != base::kNullProcessHandle) {
428 base::KillProcess(process_handle_, -1, false /* wait */);
429 LOG(ERROR) << "Killing ibus-daemon. PID="
430 << base::GetProcId(process_handle_);
431 } else {
432 // The daemon hasn't been started yet.
433 }
434
435 process_handle_ = base::kNullProcessHandle;
436 should_launch_daemon_ = false;
437 return true;
438 }
439
440 bool IBusControllerImpl::ChangeInputMethod(const std::string& id) {
Yusuke Sato 2012/04/12 08:56:50 rewritten. the key point here is to copy |id| to |
441 DCHECK(should_launch_daemon_);
442
443 // Sanity checks.
444 DCHECK(!InputMethodUtil::IsKeyboardLayout(id));
445 if (!whitelist_.InputMethodIdIsWhitelisted(id) &&
446 !InputMethodUtil::IsExtensionInputMethod(id))
447 return false;
448
449 current_input_method_id_ = id;
450
451 // Clear all input method properties unconditionally.
452 //
453 // When switching to another input method and no text area is focused,
454 // RegisterProperties signal for the new input method will NOT be sent
455 // until a text area is focused. Therefore, we have to clear the old input
456 // method properties here to keep the input method switcher status
457 // consistent.
458 RegisterProperties(NULL, NULL);
459
460 if (!IBusConnectionsAreAlive()) {
461 LOG(INFO) << "ChangeInputMethod: IBus connection is not alive (yet).";
462 // |id| will become usable shortly since Start() has already been called.
463 // Just return true.
464 } else {
465 SendChangeInputMethodRequest(id);
466 }
467
468 return true;
469 }
470
471 bool IBusControllerImpl::ActivateInputMethodProperty(const std::string& key) {
Yusuke Sato 2012/04/12 08:56:50 renamed. the second parameter is removed. line 481
472 if (!IBusConnectionsAreAlive()) {
473 LOG(ERROR) << "ActivateInputMethodProperty: IBus connection is not alive";
474 return false;
475 }
476 if (current_input_context_path_.empty()) {
477 LOG(ERROR) << "Input context is unknown";
478 return false;
479 }
480
481 // The third parameter of ibus_input_context_property_activate() has to be
482 // true when the |key| points to a radio button. false otherwise.
483 bool is_radio = true;
484 size_t i;
485 for (i = 0; i < current_property_list_.size(); ++i) {
486 if (current_property_list_[i].key == key) {
487 is_radio = current_property_list_[i].is_selection_item;
488 break;
489 }
490 }
491 if (i == current_property_list_.size()) {
492 LOG(ERROR) << "ActivateInputMethodProperty: unknown key: " << key;
493 return false;
494 }
495
496 IBusInputContext* context =
497 GetInputContext(current_input_context_path_, ibus_);
498 if (!context)
499 return false;
500
501 // Activate the property *asynchronously*.
502 ibus_input_context_property_activate(context, key.c_str(), is_radio);
503
504 // We don't have to call ibus_proxy_destroy(context) explicitly here,
505 // i.e. we can just call g_object_unref(context), since g_object_unref can
506 // trigger both dispose, which is overridden by src/ibusproxy.c, and
507 // finalize functions. For details, see
508 // http://library.gnome.org/devel/gobject/stable/gobject-memory.html
509 g_object_unref(context);
510
511 return true;
512 }
513
514 #if defined(USE_VIRTUAL_KEYBOARD)
515 // IBusController override.
516 void IBusControllerImpl::SendHandwritingStroke(
517 const HandwritingStroke& stroke) {
518 if (stroke.size() < 2) {
519 LOG(WARNING) << "Empty stroke data or a single dot is passed.";
520 return;
521 }
522
523 IBusInputContext* context =
524 GetInputContext(current_input_context_path_, ibus_);
525 if (!context)
526 return;
527
528 const size_t raw_stroke_size = stroke.size() * 2;
529 scoped_array<double> raw_stroke(new double[raw_stroke_size]);
530 for (size_t n = 0; n < stroke.size(); ++n) {
531 raw_stroke[n * 2] = stroke[n].first; // x
532 raw_stroke[n * 2 + 1] = stroke[n].second; // y
533 }
534 ibus_input_context_process_hand_writing_event(
535 context, raw_stroke.get(), raw_stroke_size);
536 g_object_unref(context);
537 }
538
539 // IBusController override.
540 void IBusControllerImpl::CancelHandwriting(int n_strokes) {
541 IBusInputContext* context =
542 GetInputContext(current_input_context_path_, ibus_);
543 if (!context)
544 return;
545 ibus_input_context_cancel_hand_writing(context, n_strokes);
546 g_object_unref(context);
547 }
548 #endif
549
550 bool IBusControllerImpl::IBusConnectionsAreAlive() {
Yusuke Sato 2012/04/12 08:56:50 rewritten. check process_handle_ as well.
551 return (process_handle_ != base::kNullProcessHandle) &&
552 ibus_ && ibus_bus_is_connected(ibus_) && ibus_config_;
553 }
554
555 void IBusControllerImpl::MaybeRestoreConnections() {
Yusuke Sato 2012/04/12 08:56:50 rewritten
556 if (IBusConnectionsAreAlive())
557 return;
558 MaybeRestoreIBusConfig();
559 if (IBusConnectionsAreAlive()) {
560 LOG(INFO) << "ibus-daemon and ibus-memconf processes are ready.";
561 ConnectPanelServiceSignals();
562 SendAllInputMethodConfigs();
563 if (!current_input_method_id_.empty())
564 SendChangeInputMethodRequest(current_input_method_id_);
565 }
566 }
567
568 void IBusControllerImpl::MaybeInitializeIBusBus() {
Yusuke Sato 2012/04/12 08:56:50 renamed but unchanged.
569 if (ibus_)
570 return;
571
572 ibus_init();
573 // Establish IBus connection between ibus-daemon to change the current input
574 // method engine, properties, and so on.
575 ibus_ = ibus_bus_new();
576 DCHECK(ibus_);
577
578 // Register callback functions for IBusBus signals.
579 ConnectBusSignals();
580
581 // Ask libibus to watch the NameOwnerChanged signal *asynchronously*.
582 ibus_bus_set_watch_dbus_signal(ibus_, TRUE);
583
584 if (ibus_bus_is_connected(ibus_)) {
585 LOG(ERROR) << "IBus connection is ready: ibus-daemon is already running?";
586 BusConnected(ibus_);
587 }
588 }
589
590 void IBusControllerImpl::MaybeRestoreIBusConfig() {
Yusuke Sato 2012/04/12 08:56:50 unchanged
591 if (!ibus_)
592 return;
593
594 // Destroy the current |ibus_config_| object. No-op if it's NULL.
595 MaybeDestroyIBusConfig();
596
597 if (ibus_config_)
598 return;
599
600 GDBusConnection* ibus_connection = ibus_bus_get_connection(ibus_);
601 if (!ibus_connection) {
602 VLOG(1) << "Couldn't create an ibus config object since "
603 << "IBus connection is not ready.";
604 return;
605 }
606
607 const gboolean disconnected
608 = g_dbus_connection_is_closed(ibus_connection);
609 if (disconnected) {
610 // |ibus_| object is not NULL, but the connection between ibus-daemon
611 // is not yet established. In this case, we don't create |ibus_config_|
612 // object.
613 LOG(ERROR) << "Couldn't create an ibus config object since "
614 << "IBus connection is closed.";
615 return;
616 }
617 // If memconf is not successfully started yet, ibus_config_new() will
618 // return NULL. Otherwise, it returns a transfer-none and non-floating
619 // object. ibus_config_new() sometimes issues a D-Bus *synchronous* IPC
620 // to check if the org.freedesktop.IBus.Config service is available.
621 ibus_config_ = ibus_config_new(ibus_connection,
622 NULL /* do not cancel the operation */,
623 NULL /* do not get error information */);
624 if (!ibus_config_) {
625 LOG(ERROR) << "ibus_config_new() failed. ibus-memconf is not ready?";
626 return;
627 }
628
629 // TODO(yusukes): g_object_weak_ref might be better since it allows
630 // libcros to detect the delivery of the "destroy" glib signal the
631 // |ibus_config_| object.
632 g_object_ref(ibus_config_);
633 VLOG(1) << "ibus_config_ is ready.";
634 }
635
636 void IBusControllerImpl::MaybeDestroyIBusConfig() {
Yusuke Sato 2012/04/12 08:56:50 unchanged
637 if (!ibus_) {
638 LOG(ERROR) << "MaybeDestroyIBusConfig: ibus_ is NULL";
639 return;
640 }
641 if (ibus_config_ && !ibus_bus_is_connected(ibus_)) {
642 g_object_unref(ibus_config_);
643 ibus_config_ = NULL;
644 }
645 }
646
647 void IBusControllerImpl::SendChangeInputMethodRequest(const std::string& id) {
Yusuke Sato 2012/04/12 08:56:50 new function
648 // Change the global engine *asynchronously*.
649 ibus_bus_set_global_engine_async(ibus_,
650 id.c_str(),
651 -1, // use the default ibus timeout
652 NULL, // cancellable
653 NULL, // callback
654 NULL); // user_data
655 }
656
657 void IBusControllerImpl::SendAllInputMethodConfigs() {
Yusuke Sato 2012/04/12 08:56:50 new function
658 DCHECK(IBusConnectionsAreAlive());
659
660 InputMethodConfigRequests::const_iterator iter =
661 current_config_values_.begin();
662 for (; iter != current_config_values_.end(); ++iter) {
663 SetInputMethodConfigInternal(iter->first, iter->second);
664 }
665 }
666
667 bool IBusControllerImpl::SetInputMethodConfigInternal(
Yusuke Sato 2012/04/12 08:56:50 renamed and slightly modified/simplified
668 const ConfigKeyType& key,
669 const InputMethodConfigValue& value) {
670 if (!IBusConnectionsAreAlive())
671 return true;
672
673 // Convert the type of |value| from our structure to GVariant.
674 GVariant* variant = NULL;
675 switch (value.type) {
676 case InputMethodConfigValue::kValueTypeString:
677 variant = g_variant_new_string(value.string_value.c_str());
678 break;
679 case InputMethodConfigValue::kValueTypeInt:
680 variant = g_variant_new_int32(value.int_value);
681 break;
682 case InputMethodConfigValue::kValueTypeBool:
683 variant = g_variant_new_boolean(value.bool_value);
684 break;
685 case InputMethodConfigValue::kValueTypeStringList:
686 GVariantBuilder variant_builder;
687 g_variant_builder_init(&variant_builder, G_VARIANT_TYPE("as"));
688 const size_t size = value.string_list_value.size();
689 // |size| could be 0 for some special configurations such as IBus hotkeys.
690 for (size_t i = 0; i < size; ++i) {
691 g_variant_builder_add(&variant_builder,
692 "s",
693 value.string_list_value[i].c_str());
694 }
695 variant = g_variant_builder_end(&variant_builder);
696 break;
697 }
698
699 if (!variant) {
700 LOG(ERROR) << "SendInputMethodConfig: unknown value.type";
701 return false;
702 }
703 DCHECK(g_variant_is_floating(variant));
704 DCHECK(ibus_config_);
705
706 // Set an ibus configuration value *asynchronously*.
707 ibus_config_set_value_async(ibus_config_,
708 key.first.c_str(),
709 key.second.c_str(),
710 variant,
711 -1, // use the default ibus timeout
712 NULL, // cancellable
713 SetInputMethodConfigCallback,
714 g_object_ref(ibus_config_));
715
716 // Since |variant| is floating, ibus_config_set_value_async consumes
717 // (takes ownership of) the variable.
718 return true;
719 }
720
721 void IBusControllerImpl::ConnectBusSignals() {
Yusuke Sato 2012/04/12 08:56:50 unchanged
722 if (!ibus_)
723 return;
724
725 // We use g_signal_connect_after here since the callback should be called
726 // *after* the IBusBusDisconnectedCallback in chromeos_input_method_ui.cc
727 // is called. chromeos_input_method_ui.cc attaches the panel service object
728 // to |ibus_|, and the callback in this file use the attached object.
729 g_signal_connect_after(ibus_,
730 "connected",
731 G_CALLBACK(BusConnectedThunk),
732 this);
733
734 g_signal_connect(ibus_,
735 "disconnected",
736 G_CALLBACK(BusDisconnectedThunk),
737 this);
738
739 g_signal_connect(ibus_,
740 "name-owner-changed",
741 G_CALLBACK(BusNameOwnerChangedThunk),
742 this);
743 }
744
745 void IBusControllerImpl::ConnectPanelServiceSignals() {
Yusuke Sato 2012/04/12 08:56:50 unchanged
746 if (!ibus_)
747 return;
748
749 IBusPanelService* ibus_panel_service = IBUS_PANEL_SERVICE(
750 g_object_get_data(G_OBJECT(ibus_), kPanelObjectKey));
751 if (!ibus_panel_service) {
752 LOG(ERROR) << "IBusPanelService is NOT available.";
753 return;
754 }
755 // We don't _ref() or _weak_ref() the panel service object, since we're not
756 // interested in the life time of the object.
757
758 g_signal_connect(ibus_panel_service,
759 "focus-in",
760 G_CALLBACK(FocusInThunk),
761 this);
762 g_signal_connect(ibus_panel_service,
763 "register-properties",
764 G_CALLBACK(RegisterPropertiesThunk),
765 this);
766 g_signal_connect(ibus_panel_service,
767 "update-property",
768 G_CALLBACK(UpdatePropertyThunk),
769 this);
770 }
771
772 void IBusControllerImpl::BusConnected(IBusBus* bus) {
Yusuke Sato 2012/04/12 08:56:50 unchanged
773 LOG(INFO) << "IBus connection is established.";
774 MaybeRestoreConnections();
775 }
776
777 void IBusControllerImpl::BusDisconnected(IBusBus* bus) {
Yusuke Sato 2012/04/12 08:56:50 removed FOR_EACH_OBSERVER
778 LOG(INFO) << "IBus connection is terminated.";
779 // ibus-daemon might be terminated. Since |ibus_| object will automatically
780 // connect to the daemon if it restarts, we don't have to set NULL on ibus_.
781 // Call MaybeDestroyIBusConfig() to set |ibus_config_| to NULL temporarily.
782 MaybeDestroyIBusConfig();
783 }
784
785 void IBusControllerImpl::BusNameOwnerChanged(IBusBus* bus,
Yusuke Sato 2012/04/12 08:56:50 unchanged
786 const gchar* name,
787 const gchar* old_name,
788 const gchar* new_name) {
789 DCHECK(name);
790 DCHECK(old_name);
791 DCHECK(new_name);
792
793 if (name != std::string("org.freedesktop.IBus.Config")) {
794 // Not a signal for ibus-memconf.
795 return;
796 }
797
798 const std::string empty_string;
799 if (old_name != empty_string || new_name == empty_string) {
800 // ibus-memconf died?
801 LOG(WARNING) << "Unexpected name owner change: name=" << name
802 << ", old_name=" << old_name << ", new_name=" << new_name;
803 // TODO(yusukes): it might be nice to set |ibus_config_| to NULL and call
804 // a new callback function like OnDisconnect() here to allow Chrome to
805 // recover all input method configurations when ibus-memconf is
806 // automatically restarted by ibus-daemon. Though ibus-memconf is pretty
807 // stable and unlikely crashes.
808 return;
809 }
810 VLOG(1) << "IBus config daemon is started. Recovering ibus_config_";
811
812 // Try to recover |ibus_config_|. If the |ibus_config_| object is
813 // successfully created, |OnConnectionChange| will be called to
814 // notify Chrome that IBus is ready.
815 MaybeRestoreConnections();
816 }
817
818 void IBusControllerImpl::FocusIn(IBusPanelService* panel,
Yusuke Sato 2012/04/12 08:56:50 unchanged
819 const gchar* input_context_path) {
820 if (!input_context_path)
821 LOG(ERROR) << "NULL context passed";
822 else
823 VLOG(1) << "FocusIn: " << input_context_path;
824 // Remember the current ic path.
825 current_input_context_path_ = Or(input_context_path, "");
826 }
827
828 void IBusControllerImpl::RegisterProperties(IBusPanelService* panel,
Yusuke Sato 2012/04/12 08:56:50 rewritten
829 IBusPropList* ibus_prop_list) {
830 // Note: |panel| can be NULL. See ChangeInputMethod().
831 VLOG(1) << "RegisterProperties" << (ibus_prop_list ? "" : " (clear)");
832
833 current_property_list_.clear();
834 if (ibus_prop_list) {
835 // You can call
836 // LOG(INFO) << "\n" << PrintPropList(ibus_prop_list, 0);
837 // here to dump |ibus_prop_list|.
838 if (!FlattenPropertyList(ibus_prop_list, &current_property_list_)) {
839 // Clear properties on errors.
840 current_property_list_.clear();
841 }
842 }
843 FOR_EACH_OBSERVER(Observer, observers_, PropertyChanged());
844 }
845
846 void IBusControllerImpl::UpdateProperty(IBusPanelService* panel,
Yusuke Sato 2012/04/12 08:56:50 rewritten
847 IBusProperty* ibus_prop) {
848 VLOG(1) << "UpdateProperty";
849 DCHECK(ibus_prop);
850
851 // You can call
852 // LOG(INFO) << "\n" << PrintProp(ibus_prop, 0);
853 // here to dump |ibus_prop|.
854
855 InputMethodPropertyList prop_list; // our representation.
856 if (!FlattenProperty(ibus_prop, &prop_list)) {
857 // Don't update the UI on errors.
858 LOG(ERROR) << "Malformed properties are detected";
859 return;
860 }
861
862 // Notify the change.
863 if (!prop_list.empty()) {
864 for (size_t i = 0; i < prop_list.size(); ++i) {
865 FindAndUpdateProperty(prop_list[i], &current_property_list_);
866 }
867 FOR_EACH_OBSERVER(Observer, observers_, PropertyChanged());
868 }
869 }
870
871 bool IBusControllerImpl::MaybeLaunchIBusDaemon() {
Yusuke Sato 2012/04/12 08:56:50 moved from the manager
872 static const char kIBusDaemonPath[] = "/usr/bin/ibus-daemon";
873
874 if (process_handle_ != base::kNullProcessHandle) {
875 LOG(ERROR) << "MaybeLaunchIBusDaemon: ibus-daemon is already running.";
876 return false;
877 }
878 if (!should_launch_daemon_)
879 return false;
880
881 // TODO(zork): Send output to /var/log/ibus.log
882 const std::string ibus_daemon_command_line =
883 base::StringPrintf("%s --panel=disable --cache=none --restart --replace",
884 kIBusDaemonPath);
885 if (!LaunchProcess(ibus_daemon_command_line,
886 &process_handle_,
887 reinterpret_cast<GChildWatchFunc>(OnIBusDaemonExit))) {
888 LOG(ERROR) << "Failed to launch " << ibus_daemon_command_line;
889 return false;
890 }
891 return true;
892 }
893
894 bool IBusControllerImpl::LaunchProcess(const std::string& command_line,
Yusuke Sato 2012/04/12 08:56:50 moved from the manager
895 base::ProcessHandle* process_handle,
896 GChildWatchFunc watch_func) {
897 std::vector<std::string> argv;
898 base::ProcessHandle handle = base::kNullProcessHandle;
899
900 base::SplitString(command_line, ' ', &argv);
901
902 if (!base::LaunchProcess(argv, base::LaunchOptions(), &handle)) {
903 LOG(ERROR) << "Could not launch: " << command_line;
904 return false;
905 }
906
907 // g_child_watch_add is necessary to prevent the process from becoming a
908 // zombie.
909 // TODO(yusukes): port g_child_watch_add to base/process_utils_posix.cc.
910 const base::ProcessId pid = base::GetProcId(handle);
911 g_child_watch_add(pid, watch_func, this);
912
913 *process_handle = handle;
914 VLOG(1) << command_line << "is started. PID=" << pid;
915 return true;
916 }
917
918 // static
919 void IBusControllerImpl::SetInputMethodConfigCallback(GObject* source_object,
Yusuke Sato 2012/04/12 08:56:50 unchanged
920 GAsyncResult* res,
921 gpointer user_data) {
922 IBusConfig* config = IBUS_CONFIG(user_data);
923 g_return_if_fail(config);
924
925 GError* error = NULL;
926 const gboolean result =
927 ibus_config_set_value_async_finish(config, res, &error);
928
929 if (!result) {
930 std::string message = "(unknown error)";
931 if (error && error->message) {
932 message = error->message;
933 }
934 LOG(ERROR) << "ibus_config_set_value_async failed: " << message;
935 }
936
937 if (error)
938 g_error_free(error);
939 g_object_unref(config);
940 }
941
942 // static
943 void IBusControllerImpl::OnIBusDaemonExit(GPid pid,
Yusuke Sato 2012/04/12 08:56:50 moved from the manager, slightly modified.
944 gint status,
945 IBusControllerImpl* controller) {
946 if (controller->process_handle_ != base::kNullProcessHandle &&
947 base::GetProcId(controller->process_handle_) == pid) {
948 controller->process_handle_ = base::kNullProcessHandle;
949 }
950 // Restart the daemon if needed.
951 controller->MaybeLaunchIBusDaemon();
952 }
953 #endif // defined(HAVE_IBUS)
954
955 // static
956 bool IBusControllerImpl::FindAndUpdatePropertyForTesting(
Yusuke Sato 2012/04/12 08:56:50 new function for testing.
957 const chromeos::input_method::InputMethodProperty& new_prop,
958 chromeos::input_method::InputMethodPropertyList* prop_list) {
959 return FindAndUpdateProperty(new_prop, prop_list);
960 }
961
962 } // namespace input_method
963 } // namespace chromeos
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698