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/input_method_engine_ibus.h" | |
6 | |
7 #include <map> | |
8 | |
9 #include "base/logging.h" | |
10 #include "base/memory/scoped_ptr.h" | |
11 #include "base/string_number_conversions.h" | |
12 #include "base/string_util.h" | |
13 #include "chrome/browser/chromeos/input_method/ibus_keymap.h" | |
14 #include "chrome/browser/chromeos/input_method/input_method_manager.h" | |
15 #include "chrome/browser/chromeos/input_method/input_method_util.h" | |
16 #include "chromeos/dbus/dbus_thread_manager.h" | |
17 #include "chromeos/dbus/ibus/ibus_client.h" | |
18 #include "chromeos/dbus/ibus/ibus_component.h" | |
19 #include "chromeos/dbus/ibus/ibus_engine_factory_service.h" | |
20 #include "chromeos/dbus/ibus/ibus_engine_service.h" | |
21 #include "chromeos/dbus/ibus/ibus_lookup_table.h" | |
22 #include "chromeos/dbus/ibus/ibus_property.h" | |
23 #include "chromeos/dbus/ibus/ibus_text.h" | |
24 #include "dbus/object_path.h" | |
25 | |
26 namespace chromeos { | |
27 const char* kExtensionImePrefix = "_ext_ime_"; | |
28 const char* kErrorNotActive = "IME is not active"; | |
29 const char* kErrorWrongContext = "Context is not active"; | |
30 const char* kCandidateNotFound = "Candidate not found"; | |
31 const char* kEngineBusPrefix = "org.freedesktop.IBus."; | |
32 | |
33 namespace { | |
34 const uint32 kIBusAltKeyMask = 1 << 3; | |
35 const uint32 kIBusCtrlKeyMask = 1 << 2; | |
36 const uint32 kIBusShiftKeyMask = 1 << 0; | |
37 const uint32 kIBusKeyReleaseMask = 1 << 30; | |
38 } | |
39 | |
40 InputMethodEngineIBus::InputMethodEngineIBus() | |
41 : focused_(false), | |
42 active_(false), | |
43 context_id_(0), | |
44 next_context_id_(1), | |
45 current_object_path_(0), | |
46 aux_text_(new ibus::IBusText()), | |
47 aux_text_visible_(false), | |
48 observer_(NULL), | |
49 preedit_text_(new ibus::IBusText()), | |
50 preedit_cursor_(0), | |
51 component_(new ibus::IBusComponent()), | |
52 table_(new ibus::IBusLookupTable()), | |
53 table_visible_(false), | |
54 weak_ptr_factory_(this) { | |
55 } | |
56 | |
57 void InputMethodEngineIBus::Initialize( | |
58 InputMethodEngine::Observer* observer, | |
59 const char* engine_name, | |
60 const char* extension_id, | |
61 const char* engine_id, | |
62 const char* description, | |
63 const char* language, | |
64 const std::vector<std::string>& layouts, | |
65 std::string* error) { | |
66 DCHECK(observer) << "Observer must not be null."; | |
67 | |
68 observer_ = observer; | |
69 engine_id_ = engine_id; | |
70 ibus_id_ = kExtensionImePrefix; | |
71 ibus_id_ += extension_id; | |
72 ibus_id_ += engine_id; | |
73 | |
74 input_method::InputMethodManager* manager = | |
75 input_method::InputMethodManager::GetInstance(); | |
76 std::string layout; | |
77 if (!layouts.empty()) { | |
78 layout = JoinString(layouts, ','); | |
79 } else { | |
80 input_method::InputMethodManager* manager = | |
81 input_method::InputMethodManager::GetInstance(); | |
82 const std::string fallback_id = | |
83 manager->GetInputMethodUtil()->GetHardwareInputMethodId(); | |
84 const input_method::InputMethodDescriptor* fallback_desc = | |
85 manager->GetInputMethodUtil()->GetInputMethodDescriptorFromId( | |
86 fallback_id); | |
87 layout = fallback_desc->keyboard_layout(); | |
88 } | |
89 | |
90 component_.reset(new ibus::IBusComponent()); | |
91 component_->set_name(std::string(kEngineBusPrefix) + std::string(engine_id)); | |
92 component_->set_description(description); | |
93 component_->set_author(engine_name); | |
94 | |
95 chromeos::ibus::IBusComponent::EngineDescription engine_desc; | |
96 engine_desc.engine_id = ibus_id_; | |
97 engine_desc.display_name = description; | |
98 engine_desc.description = description; | |
99 engine_desc.language_code = language; | |
100 engine_desc.author = ibus_id_; | |
101 engine_desc.layout = layout.c_str(); | |
102 | |
103 component_->mutable_engine_description()->push_back(engine_desc); | |
104 manager->AddInputMethodExtension(ibus_id_, engine_name, layouts, language, | |
105 this); | |
106 // If connection is avaiable, register component. If there are no connection | |
107 // to ibus-daemon, OnConnected callback will register component instead. | |
108 if (IsConnected()) | |
109 RegisterComponent(); | |
110 } | |
111 | |
112 InputMethodEngineIBus::~InputMethodEngineIBus() { | |
113 } | |
114 | |
115 bool InputMethodEngineIBus::SetComposition( | |
116 int context_id, | |
117 const char* text, | |
118 int selection_start, | |
119 int selection_end, | |
120 int cursor, | |
121 const std::vector<SegmentInfo>& segments, | |
122 std::string* error) { | |
123 if (!active_) { | |
124 *error = kErrorNotActive; | |
125 return false; | |
126 } | |
127 if (context_id != context_id_ || context_id_ == -1) { | |
128 *error = kErrorWrongContext; | |
129 return false; | |
130 } | |
131 | |
132 preedit_cursor_ = cursor; | |
133 preedit_text_.reset(new ibus::IBusText()); | |
134 preedit_text_->set_text(text); | |
135 | |
136 // TODO: Add support for displaying selected text in the composition string. | |
137 for (std::vector<SegmentInfo>::const_iterator segment = segments.begin(); | |
138 segment != segments.end(); ++segment) { | |
139 ibus::IBusText::UnderlineAttribute underline; | |
140 | |
141 switch (segment->style) { | |
142 case SEGMENT_STYLE_UNDERLINE: | |
143 underline.type = ibus::IBusText::IBUS_TEXT_UNDERLINE_SINGLE; | |
144 break; | |
145 case SEGMENT_STYLE_DOUBLE_UNDERLINE: | |
146 underline.type = ibus::IBusText::IBUS_TEXT_UNDERLINE_DOUBLE; | |
147 break; | |
148 default: | |
149 continue; | |
150 } | |
151 | |
152 underline.start_index = segment->start; | |
153 underline.end_index = segment->end; | |
154 preedit_text_->mutable_underline_attributes()->push_back(underline); | |
155 } | |
156 | |
157 // TODO(nona): Makes focus out mode configuable, if necessary. | |
158 GetCurrentService()->UpdatePreedit( | |
159 *preedit_text_.get(), | |
160 preedit_cursor_, | |
161 true, | |
162 chromeos::IBusEngineService::IBUS_ENGINE_PREEEDIT_FOCUS_OUT_MODE_COMMIT); | |
163 return true; | |
164 } | |
165 | |
166 bool InputMethodEngineIBus::ClearComposition(int context_id, | |
167 std::string* error) { | |
168 if (!active_) { | |
169 *error = kErrorNotActive; | |
170 return false; | |
171 } | |
172 if (context_id != context_id_ || context_id_ == -1) { | |
173 *error = kErrorWrongContext; | |
174 return false; | |
175 } | |
176 | |
177 preedit_cursor_ = 0; | |
178 preedit_text_.reset(new ibus::IBusText()); | |
179 GetCurrentService()->UpdatePreedit( | |
180 *preedit_text_.get(), | |
181 0, | |
182 true, | |
183 chromeos::IBusEngineService::IBUS_ENGINE_PREEEDIT_FOCUS_OUT_MODE_COMMIT); | |
184 return true; | |
185 } | |
186 | |
187 bool InputMethodEngineIBus::CommitText(int context_id, const char* text, | |
188 std::string* error) { | |
189 if (!active_) { | |
190 // TODO: Commit the text anyways. | |
191 *error = kErrorNotActive; | |
192 return false; | |
193 } | |
194 if (context_id != context_id_ || context_id_ == -1) { | |
195 *error = kErrorWrongContext; | |
196 return false; | |
197 } | |
198 | |
199 GetCurrentService()->CommitText(text); | |
200 return true; | |
201 } | |
202 | |
203 bool InputMethodEngineIBus::SetCandidateWindowVisible(bool visible, | |
204 std::string* error) { | |
205 if (!active_) { | |
206 *error = kErrorNotActive; | |
207 return false; | |
208 } | |
209 | |
210 table_visible_ = visible; | |
211 GetCurrentService()->UpdateLookupTable(*table_.get(), table_visible_); | |
212 return true; | |
213 } | |
214 | |
215 void InputMethodEngineIBus::SetCandidateWindowCursorVisible(bool visible) { | |
216 if (!active_) | |
217 return; | |
218 table_->set_is_cursor_visible(visible); | |
219 GetCurrentService()->UpdateLookupTable(*table_.get(), table_visible_); | |
220 } | |
221 | |
222 void InputMethodEngineIBus::SetCandidateWindowVertical(bool vertical) { | |
223 if (!active_) | |
224 return; | |
225 table_->set_orientation( | |
226 vertical ? ibus::IBusLookupTable::IBUS_LOOKUP_TABLE_ORIENTATION_VERTICAL : | |
227 ibus::IBusLookupTable::IBUS_LOOKUP_TABLE_ORIENTATION_HORIZONTAL); | |
228 GetCurrentService()->UpdateLookupTable(*table_.get(), table_visible_); | |
229 } | |
230 | |
231 void InputMethodEngineIBus::SetCandidateWindowPageSize(int size) { | |
232 if (!active_) | |
233 return; | |
234 table_->set_page_size(size); | |
235 GetCurrentService()->UpdateLookupTable(*table_.get(), table_visible_); | |
236 } | |
237 | |
238 void InputMethodEngineIBus::SetCandidateWindowAuxText(const char* text) { | |
239 if (!active_) | |
240 return; | |
241 aux_text_->set_text(text); | |
242 GetCurrentService()->UpdateAuxiliaryText(*aux_text_.get(), aux_text_visible_); | |
243 } | |
244 | |
245 void InputMethodEngineIBus::SetCandidateWindowAuxTextVisible(bool visible) { | |
246 if (!active_) | |
247 return; | |
248 aux_text_visible_ = visible; | |
249 GetCurrentService()->UpdateAuxiliaryText(*aux_text_.get(), aux_text_visible_); | |
250 } | |
251 | |
252 bool InputMethodEngineIBus::SetCandidates( | |
253 int context_id, | |
254 const std::vector<Candidate>& candidates, | |
255 std::string* error) { | |
256 if (!active_) { | |
257 *error = kErrorNotActive; | |
258 return false; | |
259 } | |
260 if (context_id != context_id_ || context_id_ == -1) { | |
261 *error = kErrorWrongContext; | |
262 return false; | |
263 } | |
264 | |
265 // TODO: Nested candidates | |
266 candidate_ids_.clear(); | |
267 candidate_indexes_.clear(); | |
268 table_->mutable_candidates()->clear(); | |
269 for (std::vector<Candidate>::const_iterator ix = candidates.begin(); | |
270 ix != candidates.end(); ++ix) { | |
271 ibus::IBusLookupTable::Entry entry; | |
272 // TODO(nona): support annotation(crbug.com/140186). | |
273 entry.value = ix->value + " " + ix->annotation; | |
274 entry.label = ix->label; | |
275 | |
276 // Store a mapping from the user defined ID to the candidate index. | |
277 candidate_indexes_[ix->id] = candidate_ids_.size(); | |
278 candidate_ids_.push_back(ix->id); | |
279 | |
280 table_->mutable_candidates()->push_back(entry); | |
281 } | |
282 GetCurrentService()->UpdateLookupTable(*table_.get(), table_visible_); | |
283 return true; | |
284 } | |
285 | |
286 bool InputMethodEngineIBus::SetCursorPosition(int context_id, int candidate_id, | |
287 std::string* error) { | |
288 if (!active_) { | |
289 *error = kErrorNotActive; | |
290 return false; | |
291 } | |
292 if (context_id != context_id_ || context_id_ == -1) { | |
293 *error = kErrorWrongContext; | |
294 return false; | |
295 } | |
296 | |
297 std::map<int, int>::const_iterator position = | |
298 candidate_indexes_.find(candidate_id); | |
299 if (position == candidate_indexes_.end()) { | |
300 *error = kCandidateNotFound; | |
301 return false; | |
302 } | |
303 | |
304 table_->set_cursor_position(position->second); | |
305 GetCurrentService()->UpdateLookupTable(*table_.get(), table_visible_); | |
306 return true; | |
307 } | |
308 | |
309 bool InputMethodEngineIBus::SetMenuItems(const std::vector<MenuItem>& items) { | |
310 if (!active_) | |
311 return false; | |
312 | |
313 ibus::IBusPropertyList properties; | |
314 for (std::vector<MenuItem>::const_iterator item = items.begin(); | |
315 item != items.end(); ++item) { | |
316 ibus::IBusProperty* property = new ibus::IBusProperty(); | |
317 if (!MenuItemToProperty(*item, property)) { | |
318 delete property; | |
319 DVLOG(1) << "Bad menu item"; | |
320 return false; | |
321 } | |
322 properties.push_back(property); | |
323 } | |
324 GetCurrentService()->RegisterProperties(properties); | |
325 return true; | |
326 } | |
327 | |
328 bool InputMethodEngineIBus::UpdateMenuItems( | |
329 const std::vector<MenuItem>& items) { | |
330 if (!active_) | |
331 return false; | |
332 | |
333 ibus::IBusPropertyList properties; | |
334 for (std::vector<MenuItem>::const_iterator item = items.begin(); | |
335 item != items.end(); ++item) { | |
336 ibus::IBusProperty* property = new ibus::IBusProperty(); | |
337 if (!MenuItemToProperty(*item, property)) { | |
338 delete property; | |
339 DVLOG(1) << "Bad menu item"; | |
340 return false; | |
341 } | |
342 properties.push_back(property); | |
343 } | |
344 GetCurrentService()->RegisterProperties(properties); | |
345 return true; | |
346 } | |
347 | |
348 bool InputMethodEngineIBus::IsActive() const { | |
349 return active_; | |
350 } | |
351 | |
352 void InputMethodEngineIBus::KeyEventDone(input_method::KeyEventHandle* key_data, | |
353 bool handled) { | |
354 KeyEventDoneCallback* callback = | |
355 reinterpret_cast<KeyEventDoneCallback*>(key_data); | |
356 callback->Run(handled); | |
357 delete callback; | |
358 } | |
359 | |
360 void InputMethodEngineIBus::FocusIn() { | |
361 focused_ = true; | |
362 if (!active_) | |
363 return; | |
364 context_id_ = next_context_id_; | |
365 ++next_context_id_; | |
366 | |
367 InputContext context; | |
368 context.id = context_id_; | |
369 // TODO: Other types | |
370 context.type = "text"; | |
371 | |
372 observer_->OnFocus(context); | |
373 } | |
374 | |
375 void InputMethodEngineIBus::FocusOut() { | |
376 focused_ = false; | |
377 if (!active_) | |
378 return; | |
379 int context_id = context_id_; | |
380 context_id_ = -1; | |
381 observer_->OnBlur(context_id); | |
382 } | |
383 | |
384 void InputMethodEngineIBus::Enable() { | |
385 active_ = true; | |
386 observer_->OnActivate(engine_id_); | |
387 FocusIn(); | |
388 } | |
389 | |
390 void InputMethodEngineIBus::Disable() { | |
391 active_ = false; | |
392 observer_->OnDeactivated(engine_id_); | |
393 } | |
394 | |
395 void InputMethodEngineIBus::PropertyActivate( | |
396 const std::string& property_name, | |
397 IBusPropertyState property_state) { | |
398 observer_->OnMenuItemActivated(engine_id_, property_name); | |
399 } | |
400 | |
401 void InputMethodEngineIBus::PropertyShow( | |
402 const std::string& property_name) { | |
403 } | |
404 | |
405 void InputMethodEngineIBus::PropertyHide( | |
406 const std::string& property_name) { | |
407 } | |
408 | |
409 void InputMethodEngineIBus::SetCapability( | |
410 IBusCapability capability) { | |
411 } | |
412 | |
413 void InputMethodEngineIBus::Reset() { | |
414 } | |
415 | |
416 void InputMethodEngineIBus::ProcessKeyEvent( | |
417 uint32 keysym, | |
418 uint32 keycode, | |
419 uint32 state, | |
420 const KeyEventDoneCallback& callback) { | |
421 | |
422 KeyEventDoneCallback *handler = new KeyEventDoneCallback(); | |
423 *handler = callback; | |
424 | |
425 KeyboardEvent event; | |
426 event.type = !(state & kIBusKeyReleaseMask) ? "keydown" : "keyup"; | |
427 event.key = input_method::GetIBusKey(keysym); | |
428 event.alt_key = state & kIBusAltKeyMask; | |
429 event.ctrl_key = state & kIBusCtrlKeyMask; | |
430 event.shift_key = state & kIBusShiftKeyMask; | |
431 observer_->OnKeyEvent( | |
432 engine_id_, | |
433 event, | |
434 reinterpret_cast<input_method::KeyEventHandle*>(handler)); | |
435 } | |
436 | |
437 void InputMethodEngineIBus::CandidateClicked( | |
438 uint32 index, | |
439 IBusMouseButton button, | |
440 uint32 state) { | |
441 if (index > candidate_ids_.size()) { | |
442 return; | |
443 } | |
444 | |
445 MouseButtonEvent pressed_button; | |
446 switch (button) { | |
447 case IBusEngineHandlerInterface::IBUS_MOUSE_BUTTON_LEFT: | |
448 pressed_button = MOUSE_BUTTON_LEFT; | |
449 break; | |
450 case IBusEngineHandlerInterface::IBUS_MOUSE_BUTTON_MIDDLE: | |
451 pressed_button = MOUSE_BUTTON_MIDDLE; | |
452 break; | |
453 case IBusEngineHandlerInterface::IBUS_MOUSE_BUTTON_RIGHT: | |
454 pressed_button = MOUSE_BUTTON_RIGHT; | |
455 break; | |
456 default: | |
457 DVLOG(1) << "Unknown button: " << button; | |
458 pressed_button = MOUSE_BUTTON_LEFT; | |
459 break; | |
460 } | |
461 | |
462 observer_->OnCandidateClicked( | |
463 engine_id_, candidate_ids_.at(index), pressed_button); | |
464 } | |
465 | |
466 void InputMethodEngineIBus::SetSurroundingText( | |
467 const std::string& text, | |
468 uint32 cursor_pos, | |
469 uint32 anchor_pos) { | |
470 } | |
471 | |
472 IBusEngineService* InputMethodEngineIBus::GetCurrentService() { | |
473 return DBusThreadManager::Get()->GetIBusEngineService(object_path_); | |
474 } | |
475 | |
476 bool InputMethodEngineIBus::MenuItemToProperty( | |
477 const MenuItem& item, | |
478 ibus::IBusProperty* property) { | |
479 property->set_key(item.id); | |
480 | |
481 if (item.modified & MENU_ITEM_MODIFIED_LABEL) { | |
482 property->set_label(item.label); | |
483 } | |
484 if (item.modified & MENU_ITEM_MODIFIED_VISIBLE) { | |
485 property->set_visible(item.visible); | |
486 } | |
487 if (item.modified & MENU_ITEM_MODIFIED_CHECKED) { | |
488 property->set_checked(item.checked); | |
489 } | |
490 if (item.modified & MENU_ITEM_MODIFIED_ENABLED) { | |
491 // TODO(nona): implement sensitive entry(crbug.com/140192). | |
492 } | |
493 if (item.modified & MENU_ITEM_MODIFIED_STYLE) { | |
494 ibus::IBusProperty::IBusPropertyType type = | |
495 ibus::IBusProperty::IBUS_PROPERTY_TYPE_NORMAL; | |
496 if (!item.children.empty()) { | |
497 type = ibus::IBusProperty::IBUS_PROPERTY_TYPE_MENU; | |
498 } else { | |
499 switch (item.style) { | |
500 case MENU_ITEM_STYLE_NONE: | |
501 type = ibus::IBusProperty::IBUS_PROPERTY_TYPE_NORMAL; | |
502 break; | |
503 case MENU_ITEM_STYLE_CHECK: | |
504 type = ibus::IBusProperty::IBUS_PROPERTY_TYPE_TOGGLE; | |
505 break; | |
506 case MENU_ITEM_STYLE_RADIO: | |
507 type = ibus::IBusProperty::IBUS_PROPERTY_TYPE_RADIO; | |
508 break; | |
509 case MENU_ITEM_STYLE_SEPARATOR: | |
510 type = ibus::IBusProperty::IBUS_PROPERTY_TYPE_SEPARATOR; | |
511 break; | |
512 } | |
513 } | |
514 property->set_type(type); | |
515 } | |
516 | |
517 for (std::vector<MenuItem>::const_iterator child = item.children.begin(); | |
518 child != item.children.end(); ++child) { | |
519 ibus::IBusProperty* new_property = new ibus::IBusProperty(); | |
520 if (!MenuItemToProperty(*child, new_property)) { | |
521 delete new_property; | |
522 DVLOG(1) << "Bad menu item child"; | |
523 return false; | |
524 } | |
525 property->mutable_sub_properties()->push_back(new_property); | |
526 } | |
527 | |
528 return true; | |
529 } | |
530 | |
531 void InputMethodEngineIBus::OnConnected() { | |
532 RegisterComponent(); | |
533 } | |
534 | |
535 void InputMethodEngineIBus::OnDisconnected() { | |
536 } | |
537 | |
538 bool InputMethodEngineIBus::IsConnected() { | |
539 return DBusThreadManager::Get()->GetIBusClient() != NULL; | |
540 } | |
541 | |
542 void InputMethodEngineIBus::RegisterComponent() { | |
543 chromeos::IBusClient* client = | |
544 chromeos::DBusThreadManager::Get()->GetIBusClient(); | |
545 client->RegisterComponent( | |
546 *component_.get(), | |
547 base::Bind(&InputMethodEngineIBus::OnComponentRegistered, | |
548 weak_ptr_factory_.GetWeakPtr()), | |
549 base::Bind(&InputMethodEngineIBus::OnComponentRegistrationFailed, | |
550 weak_ptr_factory_.GetWeakPtr())); | |
551 } | |
552 | |
553 void InputMethodEngineIBus::OnComponentRegistered() { | |
554 DBusThreadManager::Get()->GetIBusEngineFactoryService()-> | |
555 SetCreateEngineHandler(ibus_id_, | |
556 base::Bind( | |
557 &InputMethodEngineIBus::CreateEngineHandler, | |
558 weak_ptr_factory_.GetWeakPtr())); | |
559 } | |
560 | |
561 void InputMethodEngineIBus::OnComponentRegistrationFailed() { | |
562 // TODO(nona): Implement error handling. | |
Zachary Kuznia
2012/08/07 06:41:04
Log an error here.
Seigo Nonaka
2012/08/07 17:39:21
Done.
| |
563 } | |
564 | |
565 void InputMethodEngineIBus::CreateEngineHandler( | |
566 const IBusEngineFactoryService::CreateEngineResponseSender& sender) { | |
567 DBusThreadManager::Get()->RemoveIBusEngineService(object_path_); | |
568 | |
569 const std::string kObjectPathPrefix = "/org/freedesktop/IBus/Engine/"; | |
Zachary Kuznia
2012/08/07 06:41:04
Put this constant at the top of the file.
Seigo Nonaka
2012/08/07 17:39:21
Done.
| |
570 current_object_path_++; | |
571 object_path_ = dbus::ObjectPath(kObjectPathPrefix + | |
572 base::IntToString(current_object_path_)); | |
573 GetCurrentService()->Initialize(this); | |
574 sender.Run(object_path_); | |
575 } | |
576 | |
577 } // namespace chromeos | |
OLD | NEW |