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