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) { | |
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, ¤t_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], ¤t_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 | |
OLD | NEW |