- add sources.
[platform/framework/web/crosswalk.git] / src / chrome / browser / chromeos / input_method / input_method_engine_ibus.cc
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 #define XK_MISCELLANY
8 #include <X11/keysymdef.h>
9 #include <map>
10
11 #include "base/logging.h"
12 #include "base/memory/scoped_ptr.h"
13 #include "base/strings/string_number_conversions.h"
14 #include "base/strings/string_util.h"
15 #include "base/strings/utf_string_conversions.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_text.h"
22 #include "chromeos/ime/candidate_window.h"
23 #include "chromeos/ime/component_extension_ime_manager.h"
24 #include "chromeos/ime/extension_ime_util.h"
25 #include "chromeos/ime/ibus_keymap.h"
26 #include "chromeos/ime/input_method_manager.h"
27 #include "dbus/object_path.h"
28
29 namespace chromeos {
30 const char* kErrorNotActive = "IME is not active";
31 const char* kErrorWrongContext = "Context is not active";
32 const char* kCandidateNotFound = "Candidate not found";
33 const char* kEngineBusPrefix = "org.freedesktop.IBus.";
34
35 namespace {
36 const uint32 kIBusAltKeyMask = 1 << 3;
37 const uint32 kIBusCtrlKeyMask = 1 << 2;
38 const uint32 kIBusShiftKeyMask = 1 << 0;
39 const uint32 kIBusCapsLockMask = 1 << 1;
40 const uint32 kIBusKeyReleaseMask = 1 << 30;
41 }
42
43 InputMethodEngineIBus::InputMethodEngineIBus()
44     : focused_(false),
45       active_(false),
46       context_id_(0),
47       next_context_id_(1),
48       aux_text_(new IBusText()),
49       aux_text_visible_(false),
50       observer_(NULL),
51       preedit_text_(new IBusText()),
52       preedit_cursor_(0),
53       component_(new IBusComponent()),
54       candidate_window_(new input_method::CandidateWindow()),
55       window_visible_(false),
56       weak_ptr_factory_(this) {
57 }
58
59 InputMethodEngineIBus::~InputMethodEngineIBus() {
60   input_method::InputMethodManager::Get()->RemoveInputMethodExtension(ibus_id_);
61
62   // Do not unset engine before removing input method extension, above function
63   // may call reset function of engine object.
64   // TODO(nona): Call Reset manually here and remove relevant code from
65   //             InputMethodManager once ibus-daemon is gone. (crbug.com/158273)
66   if (!object_path_.value().empty()) {
67     GetCurrentService()->UnsetEngine(this);
68     DBusThreadManager::Get()->RemoveIBusEngineService(object_path_);
69   }
70 }
71
72 void InputMethodEngineIBus::Initialize(
73     InputMethodEngine::Observer* observer,
74     const char* engine_name,
75     const char* extension_id,
76     const char* engine_id,
77     const char* description,
78     const std::vector<std::string>& languages,
79     const std::vector<std::string>& layouts,
80     const GURL& options_page,
81     std::string* error) {
82   DCHECK(observer) << "Observer must not be null.";
83
84   observer_ = observer;
85   engine_id_ = engine_id;
86
87   input_method::InputMethodManager* manager =
88       input_method::InputMethodManager::Get();
89   ComponentExtensionIMEManager* comp_ext_ime_manager
90       = manager->GetComponentExtensionIMEManager();
91
92   if (comp_ext_ime_manager->IsInitialized() &&
93       comp_ext_ime_manager->IsWhitelistedExtension(extension_id)) {
94     ibus_id_ = comp_ext_ime_manager->GetId(extension_id, engine_id);
95   } else {
96     ibus_id_ = extension_ime_util::GetInputMethodID(extension_id, engine_id);
97   }
98
99   component_.reset(new IBusComponent());
100   component_->set_name(std::string(kEngineBusPrefix) + std::string(engine_id));
101   component_->set_description(description);
102   component_->set_author(engine_name);
103
104   // TODO(nona): Remove IBusComponent once ibus is gone.
105   IBusComponent::EngineDescription engine_desc;
106   engine_desc.engine_id = ibus_id_;
107   engine_desc.display_name = description;
108   engine_desc.description = description;
109   engine_desc.language_code = (languages.empty()) ? "" : languages[0];
110   engine_desc.author = ibus_id_;
111
112   component_->mutable_engine_description()->push_back(engine_desc);
113   manager->AddInputMethodExtension(ibus_id_, engine_name, layouts, languages,
114                                    options_page, this);
115   // If connection is avaiable, register component. If there are no connection
116   // to ibus-daemon, OnConnected callback will register component instead.
117   if (IsConnected())
118     RegisterComponent();
119 }
120
121 void InputMethodEngineIBus::StartIme() {
122   input_method::InputMethodManager* manager =
123       input_method::InputMethodManager::Get();
124   if (manager && ibus_id_ == manager->GetCurrentInputMethod().id())
125     Enable();
126 }
127
128 bool InputMethodEngineIBus::SetComposition(
129     int context_id,
130     const char* text,
131     int selection_start,
132     int selection_end,
133     int cursor,
134     const std::vector<SegmentInfo>& segments,
135     std::string* error) {
136   if (!active_) {
137     *error = kErrorNotActive;
138     return false;
139   }
140   if (context_id != context_id_ || context_id_ == -1) {
141     *error = kErrorWrongContext;
142     return false;
143   }
144
145   preedit_cursor_ = cursor;
146   preedit_text_.reset(new IBusText());
147   preedit_text_->set_text(text);
148
149   preedit_text_->mutable_selection_attributes()->clear();
150   IBusText::SelectionAttribute selection;
151   selection.start_index = selection_start;
152   selection.end_index = selection_end;
153   preedit_text_->mutable_selection_attributes()->push_back(selection);
154
155   // TODO: Add support for displaying selected text in the composition string.
156   for (std::vector<SegmentInfo>::const_iterator segment = segments.begin();
157        segment != segments.end(); ++segment) {
158     IBusText::UnderlineAttribute underline;
159
160     switch (segment->style) {
161       case SEGMENT_STYLE_UNDERLINE:
162         underline.type = IBusText::IBUS_TEXT_UNDERLINE_SINGLE;
163         break;
164       case SEGMENT_STYLE_DOUBLE_UNDERLINE:
165         underline.type = IBusText::IBUS_TEXT_UNDERLINE_DOUBLE;
166         break;
167       default:
168         continue;
169     }
170
171     underline.start_index = segment->start;
172     underline.end_index = segment->end;
173     preedit_text_->mutable_underline_attributes()->push_back(underline);
174   }
175
176   // TODO(nona): Makes focus out mode configuable, if necessary.
177   GetCurrentService()->UpdatePreedit(
178       *preedit_text_.get(),
179       preedit_cursor_,
180       true,
181       IBusEngineService::IBUS_ENGINE_PREEEDIT_FOCUS_OUT_MODE_COMMIT);
182   return true;
183 }
184
185 bool InputMethodEngineIBus::ClearComposition(int context_id,
186                                              std::string* error)  {
187   if (!active_) {
188     *error = kErrorNotActive;
189     return false;
190   }
191   if (context_id != context_id_ || context_id_ == -1) {
192     *error = kErrorWrongContext;
193     return false;
194   }
195
196   preedit_cursor_ = 0;
197   preedit_text_.reset(new IBusText());
198   GetCurrentService()->UpdatePreedit(
199       *preedit_text_.get(),
200       0,
201       false,
202       IBusEngineService::IBUS_ENGINE_PREEEDIT_FOCUS_OUT_MODE_COMMIT);
203   return true;
204 }
205
206 bool InputMethodEngineIBus::CommitText(int context_id, const char* text,
207                                        std::string* error) {
208   if (!active_) {
209     // TODO: Commit the text anyways.
210     *error = kErrorNotActive;
211     return false;
212   }
213   if (context_id != context_id_ || context_id_ == -1) {
214     *error = kErrorWrongContext;
215     return false;
216   }
217
218   IBusBridge::Get()->GetInputContextHandler()->CommitText(text);
219   return true;
220 }
221
222 const InputMethodEngineIBus::CandidateWindowProperty&
223 InputMethodEngineIBus::GetCandidateWindowProperty() const {
224   return candidate_window_property_;
225 }
226
227 void InputMethodEngineIBus::SetCandidateWindowProperty(
228     const CandidateWindowProperty& property) {
229   // Type conversion from InputMethodEngine::CandidateWindowProperty to
230   // CandidateWindow::CandidateWindowProperty defined in chromeos/ime/.
231   input_method::CandidateWindow::CandidateWindowProperty dest_property;
232   dest_property.page_size = property.page_size;
233   dest_property.is_cursor_visible = property.is_cursor_visible;
234   dest_property.is_vertical = property.is_vertical;
235   dest_property.show_window_at_composition =
236       property.show_window_at_composition;
237   dest_property.cursor_position =
238       candidate_window_->GetProperty().cursor_position;
239   candidate_window_->SetProperty(dest_property);
240   candidate_window_property_ = property;
241
242   if (active_) {
243     IBusPanelCandidateWindowHandlerInterface* cw_handler =
244         IBusBridge::Get()->GetCandidateWindowHandler();
245     if (cw_handler)
246       cw_handler->UpdateLookupTable(*candidate_window_, window_visible_);
247   }
248 }
249
250 bool InputMethodEngineIBus::SetCandidateWindowVisible(bool visible,
251                                                       std::string* error) {
252   if (!active_) {
253     *error = kErrorNotActive;
254     return false;
255   }
256
257   window_visible_ = visible;
258   IBusPanelCandidateWindowHandlerInterface* cw_handler =
259     IBusBridge::Get()->GetCandidateWindowHandler();
260   if (cw_handler)
261     cw_handler->UpdateLookupTable(*candidate_window_, window_visible_);
262   return true;
263 }
264
265 void InputMethodEngineIBus::SetCandidateWindowAuxText(const char* text) {
266   aux_text_->set_text(text);
267   if (active_) {
268     // Should not show auxiliary text if the whole window visibility is false.
269     GetCurrentService()->UpdateAuxiliaryText(
270         *aux_text_.get(),
271         window_visible_ && aux_text_visible_);
272   }
273 }
274
275 void InputMethodEngineIBus::SetCandidateWindowAuxTextVisible(bool visible) {
276   aux_text_visible_ = visible;
277   if (active_) {
278     // Should not show auxiliary text if the whole window visibility is false.
279     GetCurrentService()->UpdateAuxiliaryText(
280         *aux_text_.get(),
281         window_visible_ && aux_text_visible_);
282   }
283 }
284
285 bool InputMethodEngineIBus::SetCandidates(
286     int context_id,
287     const std::vector<Candidate>& candidates,
288     std::string* error) {
289   if (!active_) {
290     *error = kErrorNotActive;
291     return false;
292   }
293   if (context_id != context_id_ || context_id_ == -1) {
294     *error = kErrorWrongContext;
295     return false;
296   }
297
298   // TODO: Nested candidates
299   candidate_ids_.clear();
300   candidate_indexes_.clear();
301   candidate_window_->mutable_candidates()->clear();
302   for (std::vector<Candidate>::const_iterator ix = candidates.begin();
303        ix != candidates.end(); ++ix) {
304     input_method::CandidateWindow::Entry entry;
305     entry.value = ix->value;
306     entry.label = ix->label;
307     entry.annotation = ix->annotation;
308     entry.description_title = ix->usage.title;
309     entry.description_body = ix->usage.body;
310
311     // Store a mapping from the user defined ID to the candidate index.
312     candidate_indexes_[ix->id] = candidate_ids_.size();
313     candidate_ids_.push_back(ix->id);
314
315     candidate_window_->mutable_candidates()->push_back(entry);
316   }
317   if (active_) {
318     IBusPanelCandidateWindowHandlerInterface* cw_handler =
319       IBusBridge::Get()->GetCandidateWindowHandler();
320     if (cw_handler)
321       cw_handler->UpdateLookupTable(*candidate_window_, window_visible_);
322   }
323   return true;
324 }
325
326 bool InputMethodEngineIBus::SetCursorPosition(int context_id, int candidate_id,
327                                               std::string* error) {
328   if (!active_) {
329     *error = kErrorNotActive;
330     return false;
331   }
332   if (context_id != context_id_ || context_id_ == -1) {
333     *error = kErrorWrongContext;
334     return false;
335   }
336
337   std::map<int, int>::const_iterator position =
338       candidate_indexes_.find(candidate_id);
339   if (position == candidate_indexes_.end()) {
340     *error = kCandidateNotFound;
341     return false;
342   }
343
344   candidate_window_->set_cursor_position(position->second);
345   IBusPanelCandidateWindowHandlerInterface* cw_handler =
346     IBusBridge::Get()->GetCandidateWindowHandler();
347   if (cw_handler)
348     cw_handler->UpdateLookupTable(*candidate_window_, window_visible_);
349   return true;
350 }
351
352 bool InputMethodEngineIBus::SetMenuItems(const std::vector<MenuItem>& items) {
353   return UpdateMenuItems(items);
354 }
355
356 bool InputMethodEngineIBus::UpdateMenuItems(
357     const std::vector<MenuItem>& items) {
358   if (!active_)
359     return false;
360
361   input_method::InputMethodPropertyList property_list;
362   for (std::vector<MenuItem>::const_iterator item = items.begin();
363        item != items.end(); ++item) {
364     input_method::InputMethodProperty property;
365     MenuItemToProperty(*item, &property);
366     property_list.push_back(property);
367   }
368
369   IBusPanelPropertyHandlerInterface* handler =
370       IBusBridge::Get()->GetPropertyHandler();
371   if (handler)
372     handler->RegisterProperties(property_list);
373
374   return true;
375 }
376
377 bool InputMethodEngineIBus::IsActive() const {
378   return active_;
379 }
380
381 void InputMethodEngineIBus::KeyEventDone(input_method::KeyEventHandle* key_data,
382                                          bool handled) {
383   KeyEventDoneCallback* callback =
384       reinterpret_cast<KeyEventDoneCallback*>(key_data);
385   callback->Run(handled);
386   delete callback;
387 }
388
389 bool InputMethodEngineIBus::DeleteSurroundingText(int context_id,
390                                                   int offset,
391                                                   size_t number_of_chars,
392                                                   std::string* error) {
393   if (!active_) {
394     *error = kErrorNotActive;
395     return false;
396   }
397   if (context_id != context_id_ || context_id_ == -1) {
398     *error = kErrorWrongContext;
399     return false;
400   }
401
402   if (offset < 0 && static_cast<size_t>(-1 * offset) != size_t(number_of_chars))
403     return false;  // Currently we can only support preceding text.
404
405   // TODO(nona): Return false if there is ongoing composition.
406   GetCurrentService()->DeleteSurroundingText(offset, number_of_chars);
407   return true;
408 }
409
410 void InputMethodEngineIBus::FocusIn(ibus::TextInputType text_input_type) {
411   focused_ = true;
412   if (!active_)
413     return;
414   context_id_ = next_context_id_;
415   ++next_context_id_;
416
417   InputContext context;
418   context.id = context_id_;
419   // TODO: Other types
420   context.type = "text";
421
422   observer_->OnFocus(context);
423 }
424
425 void InputMethodEngineIBus::FocusOut() {
426   focused_ = false;
427   if (!active_)
428     return;
429   int context_id = context_id_;
430   context_id_ = -1;
431   observer_->OnBlur(context_id);
432 }
433
434 void InputMethodEngineIBus::Enable() {
435   active_ = true;
436   observer_->OnActivate(engine_id_);
437   FocusIn(ibus::TEXT_INPUT_TYPE_TEXT);
438
439   // Calls RequireSurroundingText once here to notify ibus-daemon to send
440   // surrounding text to this engine.
441   GetCurrentService()->RequireSurroundingText();
442 }
443
444 void InputMethodEngineIBus::Disable() {
445   active_ = false;
446   observer_->OnDeactivated(engine_id_);
447 }
448
449 void InputMethodEngineIBus::PropertyActivate(const std::string& property_name) {
450   observer_->OnMenuItemActivated(engine_id_, property_name);
451 }
452
453 void InputMethodEngineIBus::PropertyShow(
454     const std::string& property_name) {
455 }
456
457 void InputMethodEngineIBus::PropertyHide(
458     const std::string& property_name) {
459 }
460
461 void InputMethodEngineIBus::SetCapability(
462     IBusCapability capability) {
463 }
464
465 void InputMethodEngineIBus::Reset() {
466   observer_->OnReset(engine_id_);
467 }
468
469 void InputMethodEngineIBus::ProcessKeyEvent(
470     uint32 keysym,
471     uint32 keycode,
472     uint32 state,
473     const KeyEventDoneCallback& callback) {
474
475   KeyEventDoneCallback *handler = new KeyEventDoneCallback();
476   *handler = callback;
477
478   KeyboardEvent event;
479   event.type = !(state & kIBusKeyReleaseMask) ? "keydown" : "keyup";
480   event.key = input_method::GetIBusKey(keysym);
481   event.code = input_method::GetIBusKeyCode(keycode);
482   event.alt_key = state & kIBusAltKeyMask;
483   event.ctrl_key = state & kIBusCtrlKeyMask;
484   event.shift_key = state & kIBusShiftKeyMask;
485   event.caps_lock = state & kIBusCapsLockMask;
486   observer_->OnKeyEvent(
487       engine_id_,
488       event,
489       reinterpret_cast<input_method::KeyEventHandle*>(handler));
490 }
491
492 void InputMethodEngineIBus::CandidateClicked(uint32 index,
493                                              ibus::IBusMouseButton button,
494                                              uint32 state) {
495   if (index > candidate_ids_.size()) {
496     return;
497   }
498
499   MouseButtonEvent pressed_button;
500   switch (button) {
501     case ibus::IBUS_MOUSE_BUTTON_LEFT:
502       pressed_button = MOUSE_BUTTON_LEFT;
503       break;
504     case ibus::IBUS_MOUSE_BUTTON_MIDDLE:
505       pressed_button = MOUSE_BUTTON_MIDDLE;
506       break;
507     case ibus::IBUS_MOUSE_BUTTON_RIGHT:
508       pressed_button = MOUSE_BUTTON_RIGHT;
509       break;
510     default:
511       DVLOG(1) << "Unknown button: " << button;
512       pressed_button = MOUSE_BUTTON_LEFT;
513       break;
514   }
515
516   observer_->OnCandidateClicked(
517       engine_id_, candidate_ids_.at(index), pressed_button);
518 }
519
520 void InputMethodEngineIBus::SetSurroundingText(const std::string& text,
521                                                uint32 cursor_pos,
522                                                uint32 anchor_pos) {
523   observer_->OnSurroundingTextChanged(engine_id_,
524                                       text,
525                                       static_cast<int>(cursor_pos),
526                                       static_cast<int>(anchor_pos));
527 }
528
529 IBusEngineService* InputMethodEngineIBus::GetCurrentService() {
530   return DBusThreadManager::Get()->GetIBusEngineService(object_path_);
531 }
532
533 void InputMethodEngineIBus::MenuItemToProperty(
534     const MenuItem& item,
535     input_method::InputMethodProperty* property) {
536   property->key = item.id;
537
538   if (item.modified & MENU_ITEM_MODIFIED_LABEL) {
539     property->label = item.label;
540   }
541   if (item.modified & MENU_ITEM_MODIFIED_VISIBLE) {
542     // TODO(nona): Implement it.
543   }
544   if (item.modified & MENU_ITEM_MODIFIED_CHECKED) {
545     property->is_selection_item_checked = item.checked;
546   }
547   if (item.modified & MENU_ITEM_MODIFIED_ENABLED) {
548     // TODO(nona): implement sensitive entry(crbug.com/140192).
549   }
550   if (item.modified & MENU_ITEM_MODIFIED_STYLE) {
551     if (!item.children.empty()) {
552       // TODO(nona): Implement it.
553     } else {
554       switch (item.style) {
555         case MENU_ITEM_STYLE_NONE:
556           NOTREACHED();
557           break;
558         case MENU_ITEM_STYLE_CHECK:
559           // TODO(nona): Implement it.
560           break;
561         case MENU_ITEM_STYLE_RADIO:
562           property->is_selection_item = true;
563           break;
564         case MENU_ITEM_STYLE_SEPARATOR:
565           // TODO(nona): Implement it.
566           break;
567       }
568     }
569   }
570
571   // TODO(nona): Support item.children.
572 }
573
574 void InputMethodEngineIBus::OnConnected() {
575   RegisterComponent();
576 }
577
578 void InputMethodEngineIBus::OnDisconnected() {
579 }
580
581 bool InputMethodEngineIBus::IsConnected() {
582   return DBusThreadManager::Get()->GetIBusClient() != NULL;
583 }
584
585 void InputMethodEngineIBus::RegisterComponent() {
586   IBusClient* client = DBusThreadManager::Get()->GetIBusClient();
587   client->RegisterComponent(
588       *component_.get(),
589       base::Bind(&InputMethodEngineIBus::OnComponentRegistered,
590                  weak_ptr_factory_.GetWeakPtr()),
591       base::Bind(&InputMethodEngineIBus::OnComponentRegistrationFailed,
592                  weak_ptr_factory_.GetWeakPtr()));
593 }
594
595 void InputMethodEngineIBus::OnComponentRegistered() {
596   DBusThreadManager::Get()->GetIBusEngineFactoryService()->
597       SetCreateEngineHandler(ibus_id_,
598                              base::Bind(
599                                  &InputMethodEngineIBus::CreateEngineHandler,
600                                  weak_ptr_factory_.GetWeakPtr()));
601 }
602
603 void InputMethodEngineIBus::OnComponentRegistrationFailed() {
604   DVLOG(1) << "Failed to register input method components.";
605   // TODO(nona): Implement error handling.
606 }
607
608 void InputMethodEngineIBus::CreateEngineHandler(
609     const IBusEngineFactoryService::CreateEngineResponseSender& sender) {
610   GetCurrentService()->UnsetEngine(this);
611   DBusThreadManager::Get()->RemoveIBusEngineService(object_path_);
612
613   object_path_ = DBusThreadManager::Get()->GetIBusEngineFactoryService()->
614       GenerateUniqueObjectPath();
615
616   GetCurrentService()->SetEngine(this);
617   sender.Run(object_path_);
618 }
619
620 }  // namespace chromeos