Upstream version 5.34.104.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / chromeos / input_method / input_method_engine.cc
1 // Copyright 2013 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.h"
6
7 #define XK_MISCELLANY
8 #include <X11/keysymdef.h>
9 #include <X11/X.h>
10 #include <X11/Xlib.h>
11 #include <X11/Xutil.h>
12 #undef FocusIn
13 #undef FocusOut
14 #undef RootWindow
15 #include <map>
16
17 #include "ash/shell.h"
18 #include "base/logging.h"
19 #include "base/memory/scoped_ptr.h"
20 #include "base/strings/string_number_conversions.h"
21 #include "base/strings/string_util.h"
22 #include "base/strings/utf_string_conversions.h"
23 #include "chromeos/ime/component_extension_ime_manager.h"
24 #include "chromeos/ime/composition_text.h"
25 #include "chromeos/ime/extension_ime_util.h"
26 #include "chromeos/ime/ibus_keymap.h"
27 #include "chromeos/ime/input_method_manager.h"
28 #include "ui/aura/root_window.h"
29 #include "ui/aura/window.h"
30 #include "ui/base/ime/candidate_window.h"
31 #include "ui/events/event.h"
32 #include "ui/events/keycodes/dom4/keycode_converter.h"
33 #include "ui/events/keycodes/keyboard_code_conversion_x.h"
34 #include "ui/keyboard/keyboard_controller.h"
35
36 namespace chromeos {
37 const char* kErrorNotActive = "IME is not active";
38 const char* kErrorWrongContext = "Context is not active";
39 const char* kCandidateNotFound = "Candidate not found";
40
41 namespace {
42
43 // Notifies InputContextHandler that the composition is changed.
44 void UpdateComposition(const CompositionText& composition_text,
45                        uint32 cursor_pos,
46                        bool is_visible) {
47   IBusInputContextHandlerInterface* input_context =
48       IMEBridge::Get()->GetInputContextHandler();
49   if (input_context)
50     input_context->UpdateCompositionText(
51         composition_text, cursor_pos, is_visible);
52 }
53
54 }  // namespace
55
56 InputMethodEngine::InputMethodEngine()
57     : focused_(false),
58       active_(false),
59       context_id_(0),
60       next_context_id_(1),
61       observer_(NULL),
62       composition_text_(new CompositionText()),
63       composition_cursor_(0),
64       candidate_window_(new ui::CandidateWindow()),
65       window_visible_(false),
66       sent_key_event_(NULL) {}
67
68 InputMethodEngine::~InputMethodEngine() {
69   input_method::InputMethodManager::Get()->RemoveInputMethodExtension(imm_id_);
70 }
71
72 void InputMethodEngine::Initialize(
73     InputMethodEngineInterface::Observer* observer,
74     const char* engine_name,
75     const char* extension_id,
76     const char* engine_id,
77     const std::vector<std::string>& languages,
78     const std::vector<std::string>& layouts,
79     const GURL& options_page,
80     const GURL& input_view) {
81   DCHECK(observer) << "Observer must not be null.";
82
83   // TODO(komatsu): It is probably better to set observer out of Initialize.
84   observer_ = observer;
85   engine_id_ = engine_id;
86   extension_id_ = extension_id;
87
88   input_method::InputMethodManager* manager =
89       input_method::InputMethodManager::Get();
90   ComponentExtensionIMEManager* comp_ext_ime_manager =
91       manager->GetComponentExtensionIMEManager();
92
93   if (comp_ext_ime_manager->IsInitialized() &&
94       comp_ext_ime_manager->IsWhitelistedExtension(extension_id)) {
95     imm_id_ = comp_ext_ime_manager->GetId(extension_id, engine_id);
96   } else {
97     imm_id_ = extension_ime_util::GetInputMethodID(extension_id, engine_id);
98   }
99
100   input_view_url_ = input_view;
101   descriptor_ = input_method::InputMethodDescriptor(
102       imm_id_,
103       engine_name,
104       std::string(), // TODO(uekawa): Set short name.
105       layouts,
106       languages,
107       false,  // is_login_keyboard
108       options_page,
109       input_view);
110
111   // TODO(komatsu): It is probably better to call AddInputMethodExtension
112   // out of Initialize.
113   manager->AddInputMethodExtension(imm_id_, this);
114 }
115
116 const input_method::InputMethodDescriptor& InputMethodEngine::GetDescriptor()
117     const {
118   return descriptor_;
119 }
120
121 void InputMethodEngine::StartIme() {
122   input_method::InputMethodManager* manager =
123       input_method::InputMethodManager::Get();
124   if (manager && imm_id_ == manager->GetCurrentInputMethod().id())
125     Enable();
126 }
127
128 bool InputMethodEngine::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   composition_cursor_ = cursor;
146   composition_text_.reset(new CompositionText());
147   composition_text_->set_text(base::UTF8ToUTF16(text));
148
149   composition_text_->set_selection_start(selection_start);
150   composition_text_->set_selection_end(selection_end);
151
152   // TODO: Add support for displaying selected text in the composition string.
153   for (std::vector<SegmentInfo>::const_iterator segment = segments.begin();
154        segment != segments.end(); ++segment) {
155     CompositionText::UnderlineAttribute underline;
156
157     switch (segment->style) {
158       case SEGMENT_STYLE_UNDERLINE:
159         underline.type = CompositionText::COMPOSITION_TEXT_UNDERLINE_SINGLE;
160         break;
161       case SEGMENT_STYLE_DOUBLE_UNDERLINE:
162         underline.type = CompositionText::COMPOSITION_TEXT_UNDERLINE_DOUBLE;
163         break;
164       default:
165         continue;
166     }
167
168     underline.start_index = segment->start;
169     underline.end_index = segment->end;
170     composition_text_->mutable_underline_attributes()->push_back(underline);
171   }
172
173   // TODO(nona): Makes focus out mode configuable, if necessary.
174   UpdateComposition(*composition_text_, composition_cursor_, true);
175   return true;
176 }
177
178 bool InputMethodEngine::ClearComposition(int context_id,
179                                          std::string* error)  {
180   if (!active_) {
181     *error = kErrorNotActive;
182     return false;
183   }
184   if (context_id != context_id_ || context_id_ == -1) {
185     *error = kErrorWrongContext;
186     return false;
187   }
188
189   composition_cursor_ = 0;
190   composition_text_.reset(new CompositionText());
191   UpdateComposition(*composition_text_, composition_cursor_, false);
192   return true;
193 }
194
195 bool InputMethodEngine::CommitText(int context_id, const char* text,
196                                    std::string* error) {
197   if (!active_) {
198     // TODO: Commit the text anyways.
199     *error = kErrorNotActive;
200     return false;
201   }
202   if (context_id != context_id_ || context_id_ == -1) {
203     *error = kErrorWrongContext;
204     return false;
205   }
206
207   IMEBridge::Get()->GetInputContextHandler()->CommitText(text);
208   return true;
209 }
210
211 bool InputMethodEngine::SendKeyEvents(
212     int context_id,
213     const std::vector<KeyboardEvent>& events) {
214   if (!active_) {
215     return false;
216   }
217   if (context_id != context_id_ || context_id_ == -1) {
218     return false;
219   }
220
221   aura::WindowEventDispatcher* dispatcher =
222       ash::Shell::GetPrimaryRootWindow()->GetDispatcher();
223
224   for (size_t i = 0; i < events.size(); ++i) {
225     const KeyboardEvent& event = events[i];
226     const ui::EventType type =
227         (event.type == "keyup") ? ui::ET_KEY_RELEASED : ui::ET_KEY_PRESSED;
228
229     // KeyboardCodeFromXKyeSym assumes US keyboard layout.
230     ui::KeycodeConverter* conv = ui::KeycodeConverter::GetInstance();
231     DCHECK(conv);
232
233      // DOM code (KeyA) -> XKB -> XKeySym (XK_A) -> KeyboardCode (VKEY_A)
234     const uint16 native_keycode =
235         conv->CodeToNativeKeycode(event.code.c_str());
236     const uint xkeysym = ui::DefaultXKeysymFromHardwareKeycode(native_keycode);
237     const ui::KeyboardCode key_code = ui::KeyboardCodeFromXKeysym(xkeysym);
238
239     const std::string code = event.code;
240     int flags = ui::EF_NONE;
241     flags |= event.alt_key   ? ui::EF_ALT_DOWN       : ui::EF_NONE;
242     flags |= event.ctrl_key  ? ui::EF_CONTROL_DOWN   : ui::EF_NONE;
243     flags |= event.shift_key ? ui::EF_SHIFT_DOWN     : ui::EF_NONE;
244     flags |= event.caps_lock ? ui::EF_CAPS_LOCK_DOWN : ui::EF_NONE;
245
246     ui::KeyEvent ui_event(type, key_code, code, flags, false /* is_char */);
247     base::AutoReset<const ui::KeyEvent*> reset_sent_key(&sent_key_event_,
248                                                         &ui_event);
249     ui::EventDispatchDetails details = dispatcher->OnEventFromSource(&ui_event);
250     if (details.dispatcher_destroyed)
251       break;
252   }
253   return true;
254 }
255
256 const InputMethodEngine::CandidateWindowProperty&
257 InputMethodEngine::GetCandidateWindowProperty() const {
258   return candidate_window_property_;
259 }
260
261 void InputMethodEngine::SetCandidateWindowProperty(
262     const CandidateWindowProperty& property) {
263   // Type conversion from InputMethodEngineInterface::CandidateWindowProperty to
264   // CandidateWindow::CandidateWindowProperty defined in chromeos/ime/.
265   ui::CandidateWindow::CandidateWindowProperty dest_property;
266   dest_property.page_size = property.page_size;
267   dest_property.is_cursor_visible = property.is_cursor_visible;
268   dest_property.is_vertical = property.is_vertical;
269   dest_property.show_window_at_composition =
270       property.show_window_at_composition;
271   dest_property.cursor_position =
272       candidate_window_->GetProperty().cursor_position;
273   dest_property.auxiliary_text = property.auxiliary_text;
274   dest_property.is_auxiliary_text_visible = property.is_auxiliary_text_visible;
275
276   candidate_window_->SetProperty(dest_property);
277   candidate_window_property_ = property;
278
279   if (active_) {
280     IBusPanelCandidateWindowHandlerInterface* cw_handler =
281         IMEBridge::Get()->GetCandidateWindowHandler();
282     if (cw_handler)
283       cw_handler->UpdateLookupTable(*candidate_window_, window_visible_);
284   }
285 }
286
287 bool InputMethodEngine::SetCandidateWindowVisible(bool visible,
288                                                   std::string* error) {
289   if (!active_) {
290     *error = kErrorNotActive;
291     return false;
292   }
293
294   window_visible_ = visible;
295   IBusPanelCandidateWindowHandlerInterface* cw_handler =
296     IMEBridge::Get()->GetCandidateWindowHandler();
297   if (cw_handler)
298     cw_handler->UpdateLookupTable(*candidate_window_, window_visible_);
299   return true;
300 }
301
302 bool InputMethodEngine::SetCandidates(
303     int context_id,
304     const std::vector<Candidate>& candidates,
305     std::string* error) {
306   if (!active_) {
307     *error = kErrorNotActive;
308     return false;
309   }
310   if (context_id != context_id_ || context_id_ == -1) {
311     *error = kErrorWrongContext;
312     return false;
313   }
314
315   // TODO: Nested candidates
316   candidate_ids_.clear();
317   candidate_indexes_.clear();
318   candidate_window_->mutable_candidates()->clear();
319   for (std::vector<Candidate>::const_iterator ix = candidates.begin();
320        ix != candidates.end(); ++ix) {
321     ui::CandidateWindow::Entry entry;
322     entry.value = base::UTF8ToUTF16(ix->value);
323     entry.label = base::UTF8ToUTF16(ix->label);
324     entry.annotation = base::UTF8ToUTF16(ix->annotation);
325     entry.description_title = base::UTF8ToUTF16(ix->usage.title);
326     entry.description_body = base::UTF8ToUTF16(ix->usage.body);
327
328     // Store a mapping from the user defined ID to the candidate index.
329     candidate_indexes_[ix->id] = candidate_ids_.size();
330     candidate_ids_.push_back(ix->id);
331
332     candidate_window_->mutable_candidates()->push_back(entry);
333   }
334   if (active_) {
335     IBusPanelCandidateWindowHandlerInterface* cw_handler =
336       IMEBridge::Get()->GetCandidateWindowHandler();
337     if (cw_handler)
338       cw_handler->UpdateLookupTable(*candidate_window_, window_visible_);
339   }
340   return true;
341 }
342
343 bool InputMethodEngine::SetCursorPosition(int context_id, int candidate_id,
344                                           std::string* error) {
345   if (!active_) {
346     *error = kErrorNotActive;
347     return false;
348   }
349   if (context_id != context_id_ || context_id_ == -1) {
350     *error = kErrorWrongContext;
351     return false;
352   }
353
354   std::map<int, int>::const_iterator position =
355       candidate_indexes_.find(candidate_id);
356   if (position == candidate_indexes_.end()) {
357     *error = kCandidateNotFound;
358     return false;
359   }
360
361   candidate_window_->set_cursor_position(position->second);
362   IBusPanelCandidateWindowHandlerInterface* cw_handler =
363     IMEBridge::Get()->GetCandidateWindowHandler();
364   if (cw_handler)
365     cw_handler->UpdateLookupTable(*candidate_window_, window_visible_);
366   return true;
367 }
368
369 bool InputMethodEngine::SetMenuItems(const std::vector<MenuItem>& items) {
370   return UpdateMenuItems(items);
371 }
372
373 bool InputMethodEngine::UpdateMenuItems(
374     const std::vector<MenuItem>& items) {
375   if (!active_)
376     return false;
377
378   input_method::InputMethodPropertyList property_list;
379   for (std::vector<MenuItem>::const_iterator item = items.begin();
380        item != items.end(); ++item) {
381     input_method::InputMethodProperty property;
382     MenuItemToProperty(*item, &property);
383     property_list.push_back(property);
384   }
385
386   input_method::InputMethodManager* manager =
387       input_method::InputMethodManager::Get();
388   if (manager)
389     manager->SetCurrentInputMethodProperties(property_list);
390
391   return true;
392 }
393
394 bool InputMethodEngine::IsActive() const {
395   return active_;
396 }
397
398 void InputMethodEngine::KeyEventDone(input_method::KeyEventHandle* key_data,
399                                      bool handled) {
400   KeyEventDoneCallback* callback =
401       reinterpret_cast<KeyEventDoneCallback*>(key_data);
402   callback->Run(handled);
403   delete callback;
404 }
405
406 bool InputMethodEngine::DeleteSurroundingText(int context_id,
407                                               int offset,
408                                               size_t number_of_chars,
409                                               std::string* error) {
410   if (!active_) {
411     *error = kErrorNotActive;
412     return false;
413   }
414   if (context_id != context_id_ || context_id_ == -1) {
415     *error = kErrorWrongContext;
416     return false;
417   }
418
419   if (offset < 0 && static_cast<size_t>(-1 * offset) != size_t(number_of_chars))
420     return false;  // Currently we can only support preceding text.
421
422   // TODO(nona): Return false if there is ongoing composition.
423
424   IBusInputContextHandlerInterface* input_context =
425       IMEBridge::Get()->GetInputContextHandler();
426   if (input_context)
427     input_context->DeleteSurroundingText(offset, number_of_chars);
428
429   return true;
430 }
431
432 void InputMethodEngine::HideInputView() {
433   keyboard::KeyboardController* keyboard_controller =
434     ash::Shell::GetInstance()->keyboard_controller();
435   if (keyboard_controller) {
436     keyboard_controller->HideKeyboard(
437         keyboard::KeyboardController::HIDE_REASON_MANUAL);
438   }
439 }
440
441 void InputMethodEngine::FocusIn(
442     const IMEEngineHandlerInterface::InputContext& input_context) {
443   focused_ = true;
444   if (!active_)
445     return;
446   context_id_ = next_context_id_;
447   ++next_context_id_;
448
449   InputMethodEngineInterface::InputContext context;
450   context.id = context_id_;
451   switch (input_context.type) {
452     case ui::TEXT_INPUT_TYPE_SEARCH:
453       context.type = "search";
454       break;
455     case ui::TEXT_INPUT_TYPE_TELEPHONE:
456       context.type = "tel";
457       break;
458     case ui::TEXT_INPUT_TYPE_URL:
459       context.type = "url";
460       break;
461     case ui::TEXT_INPUT_TYPE_EMAIL:
462       context.type = "email";
463       break;
464     case ui::TEXT_INPUT_TYPE_NUMBER:
465       context.type = "number";
466       break;
467     default:
468       context.type = "text";
469       break;
470   }
471
472   observer_->OnFocus(context);
473 }
474
475 void InputMethodEngine::FocusOut() {
476   focused_ = false;
477   if (!active_)
478     return;
479   int context_id = context_id_;
480   context_id_ = -1;
481   observer_->OnBlur(context_id);
482 }
483
484 void InputMethodEngine::Enable() {
485   active_ = true;
486   observer_->OnActivate(engine_id_);
487   IMEEngineHandlerInterface::InputContext context(ui::TEXT_INPUT_TYPE_TEXT,
488                                                   ui::TEXT_INPUT_MODE_DEFAULT);
489   FocusIn(context);
490
491   keyboard::KeyboardController* keyboard_controller =
492       ash::Shell::GetInstance()->keyboard_controller();
493   if (keyboard_controller) {
494     keyboard_controller->SetOverrideContentUrl(input_view_url_);
495   }
496 }
497
498 void InputMethodEngine::Disable() {
499   active_ = false;
500   observer_->OnDeactivated(engine_id_);
501
502   keyboard::KeyboardController* keyboard_controller =
503       ash::Shell::GetInstance()->keyboard_controller();
504   if (keyboard_controller) {
505     GURL empty_url;
506     keyboard_controller->SetOverrideContentUrl(empty_url);
507   }
508 }
509
510 void InputMethodEngine::PropertyActivate(const std::string& property_name) {
511   observer_->OnMenuItemActivated(engine_id_, property_name);
512 }
513
514 void InputMethodEngine::Reset() {
515   observer_->OnReset(engine_id_);
516 }
517
518 namespace {
519 void GetExtensionKeyboardEventFromKeyEvent(
520     const ui::KeyEvent& event,
521     InputMethodEngine::KeyboardEvent* ext_event) {
522   DCHECK(event.type() == ui::ET_KEY_RELEASED ||
523          event.type() == ui::ET_KEY_PRESSED);
524   DCHECK(ext_event);
525   ext_event->type = (event.type() == ui::ET_KEY_RELEASED) ? "keyup" : "keydown";
526
527   ext_event->code = event.code();
528   ext_event->alt_key = event.IsAltDown();
529   ext_event->ctrl_key = event.IsControlDown();
530   ext_event->shift_key = event.IsShiftDown();
531   ext_event->caps_lock = event.IsCapsLockDown();
532
533   uint32 ibus_keyval = 0;
534   if (event.HasNativeEvent()) {
535     const base::NativeEvent& native_event = event.native_event();
536     DCHECK(native_event);
537
538     XKeyEvent* x_key = &(static_cast<XEvent*>(native_event)->xkey);
539     KeySym keysym = NoSymbol;
540     ::XLookupString(x_key, NULL, 0, &keysym, NULL);
541     ibus_keyval = keysym;
542   } else {
543     // Convert ui::KeyEvent.key_code to DOM UIEvent key.
544     // XKeysymForWindowsKeyCode converts key_code to XKeySym, but it
545     // assumes US layout and does not care about CapLock state.
546     //
547     // TODO(komatsu): Support CapsLock states.
548     // TODO(komatsu): Support non-us keyboard layouts.
549     ibus_keyval = ui::XKeysymForWindowsKeyCode(event.key_code(),
550                                                event.IsShiftDown());
551   }
552   ext_event->key = input_method::GetIBusKey(ibus_keyval);
553 }
554 }  // namespace
555
556 void InputMethodEngine::ProcessKeyEvent(
557     const ui::KeyEvent& key_event,
558     const KeyEventDoneCallback& callback) {
559
560   KeyEventDoneCallback *handler = new KeyEventDoneCallback();
561   *handler = callback;
562
563   KeyboardEvent ext_event;
564   GetExtensionKeyboardEventFromKeyEvent(key_event, &ext_event);
565
566   // If the given key event is equal to the key event sent by
567   // SendKeyEvents, this engine ID is propagated to the extension IME.
568   // Note, this check relies on that ui::KeyEvent is propagated as
569   // reference without copying.
570   if (&key_event == sent_key_event_)
571     ext_event.extension_id = extension_id_;
572
573   observer_->OnKeyEvent(
574       engine_id_,
575       ext_event,
576       reinterpret_cast<input_method::KeyEventHandle*>(handler));
577 }
578
579 void InputMethodEngine::CandidateClicked(uint32 index) {
580   if (index > candidate_ids_.size()) {
581     return;
582   }
583
584   // Only left button click is supported at this moment.
585   observer_->OnCandidateClicked(
586       engine_id_, candidate_ids_.at(index), MOUSE_BUTTON_LEFT);
587 }
588
589 void InputMethodEngine::SetSurroundingText(const std::string& text,
590                                            uint32 cursor_pos,
591                                            uint32 anchor_pos) {
592   observer_->OnSurroundingTextChanged(engine_id_,
593                                       text,
594                                       static_cast<int>(cursor_pos),
595                                       static_cast<int>(anchor_pos));
596 }
597
598 void InputMethodEngine::MenuItemToProperty(
599     const MenuItem& item,
600     input_method::InputMethodProperty* property) {
601   property->key = item.id;
602
603   if (item.modified & MENU_ITEM_MODIFIED_LABEL) {
604     property->label = item.label;
605   }
606   if (item.modified & MENU_ITEM_MODIFIED_VISIBLE) {
607     // TODO(nona): Implement it.
608   }
609   if (item.modified & MENU_ITEM_MODIFIED_CHECKED) {
610     property->is_selection_item_checked = item.checked;
611   }
612   if (item.modified & MENU_ITEM_MODIFIED_ENABLED) {
613     // TODO(nona): implement sensitive entry(crbug.com/140192).
614   }
615   if (item.modified & MENU_ITEM_MODIFIED_STYLE) {
616     if (!item.children.empty()) {
617       // TODO(nona): Implement it.
618     } else {
619       switch (item.style) {
620         case MENU_ITEM_STYLE_NONE:
621           NOTREACHED();
622           break;
623         case MENU_ITEM_STYLE_CHECK:
624           // TODO(nona): Implement it.
625           break;
626         case MENU_ITEM_STYLE_RADIO:
627           property->is_selection_item = true;
628           break;
629         case MENU_ITEM_STYLE_SEPARATOR:
630           // TODO(nona): Implement it.
631           break;
632       }
633     }
634   }
635
636   // TODO(nona): Support item.children.
637 }
638
639 }  // namespace chromeos