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.
5 #include "chrome/browser/chromeos/input_method/input_method_engine.h"
8 #include <X11/keysymdef.h>
11 #include <X11/Xutil.h>
17 #include "ash/ime/input_method_menu_item.h"
18 #include "ash/ime/input_method_menu_manager.h"
19 #include "ash/shell.h"
20 #include "base/logging.h"
21 #include "base/memory/scoped_ptr.h"
22 #include "base/strings/string_number_conversions.h"
23 #include "base/strings/string_util.h"
24 #include "base/strings/utf_string_conversions.h"
25 #include "chromeos/ime/component_extension_ime_manager.h"
26 #include "chromeos/ime/composition_text.h"
27 #include "chromeos/ime/extension_ime_util.h"
28 #include "chromeos/ime/input_method_manager.h"
29 #include "ui/aura/window.h"
30 #include "ui/aura/window_tree_host.h"
31 #include "ui/base/ime/candidate_window.h"
32 #include "ui/base/ime/chromeos/ime_keymap.h"
33 #include "ui/events/event.h"
34 #include "ui/events/event_processor.h"
35 #include "ui/events/keycodes/dom4/keycode_converter.h"
36 #include "ui/events/keycodes/keyboard_code_conversion_x.h"
37 #include "ui/keyboard/keyboard_controller.h"
38 #include "ui/keyboard/keyboard_util.h"
41 const char* kErrorNotActive
= "IME is not active";
42 const char* kErrorWrongContext
= "Context is not active";
43 const char* kCandidateNotFound
= "Candidate not found";
47 // Notifies InputContextHandler that the composition is changed.
48 void UpdateComposition(const CompositionText
& composition_text
,
51 IMEInputContextHandlerInterface
* input_context
=
52 IMEBridge::Get()->GetInputContextHandler();
54 input_context
->UpdateCompositionText(
55 composition_text
, cursor_pos
, is_visible
);
60 InputMethodEngine::InputMethodEngine()
61 : current_input_type_(ui::TEXT_INPUT_TYPE_NONE
),
65 composition_text_(new CompositionText()),
66 composition_cursor_(0),
67 candidate_window_(new ui::CandidateWindow()),
68 window_visible_(false),
69 sent_key_event_(NULL
) {}
71 InputMethodEngine::~InputMethodEngine() {
72 input_method::InputMethodManager::Get()->RemoveInputMethodExtension(imm_id_
);
75 void InputMethodEngine::Initialize(
76 scoped_ptr
<InputMethodEngineInterface::Observer
> observer
,
77 const char* engine_name
,
78 const char* extension_id
,
79 const char* engine_id
,
80 const std::vector
<std::string
>& languages
,
81 const std::vector
<std::string
>& layouts
,
82 const GURL
& options_page
,
83 const GURL
& input_view
) {
84 DCHECK(observer
) << "Observer must not be null.";
86 // TODO(komatsu): It is probably better to set observer out of Initialize.
87 observer_
= observer
.Pass();
88 engine_id_
= engine_id
;
89 extension_id_
= extension_id
;
91 input_method::InputMethodManager
* manager
=
92 input_method::InputMethodManager::Get();
93 ComponentExtensionIMEManager
* comp_ext_ime_manager
=
94 manager
->GetComponentExtensionIMEManager();
96 if (comp_ext_ime_manager
&& comp_ext_ime_manager
->IsInitialized() &&
97 comp_ext_ime_manager
->IsWhitelistedExtension(extension_id
)) {
98 imm_id_
= comp_ext_ime_manager
->GetId(extension_id
, engine_id
);
100 imm_id_
= extension_ime_util::GetInputMethodID(extension_id
, engine_id
);
103 input_view_url_
= input_view
;
104 descriptor_
= input_method::InputMethodDescriptor(
107 std::string(), // TODO(uekawa): Set short name.
110 extension_ime_util::IsKeyboardLayoutExtension(
111 imm_id_
), // is_login_keyboard
115 // TODO(komatsu): It is probably better to call AddInputMethodExtension
116 // out of Initialize.
117 manager
->AddInputMethodExtension(imm_id_
, this);
120 const input_method::InputMethodDescriptor
& InputMethodEngine::GetDescriptor()
125 void InputMethodEngine::NotifyImeReady() {
126 input_method::InputMethodManager
* manager
=
127 input_method::InputMethodManager::Get();
128 if (manager
&& imm_id_
== manager
->GetCurrentInputMethod().id())
132 bool InputMethodEngine::SetComposition(
138 const std::vector
<SegmentInfo
>& segments
,
139 std::string
* error
) {
141 *error
= kErrorNotActive
;
144 if (context_id
!= context_id_
|| context_id_
== -1) {
145 *error
= kErrorWrongContext
;
149 composition_cursor_
= cursor
;
150 composition_text_
.reset(new CompositionText());
151 composition_text_
->set_text(base::UTF8ToUTF16(text
));
153 composition_text_
->set_selection_start(selection_start
);
154 composition_text_
->set_selection_end(selection_end
);
156 // TODO: Add support for displaying selected text in the composition string.
157 for (std::vector
<SegmentInfo
>::const_iterator segment
= segments
.begin();
158 segment
!= segments
.end(); ++segment
) {
159 CompositionText::UnderlineAttribute underline
;
161 switch (segment
->style
) {
162 case SEGMENT_STYLE_UNDERLINE
:
163 underline
.type
= CompositionText::COMPOSITION_TEXT_UNDERLINE_SINGLE
;
165 case SEGMENT_STYLE_DOUBLE_UNDERLINE
:
166 underline
.type
= CompositionText::COMPOSITION_TEXT_UNDERLINE_DOUBLE
;
172 underline
.start_index
= segment
->start
;
173 underline
.end_index
= segment
->end
;
174 composition_text_
->mutable_underline_attributes()->push_back(underline
);
177 // TODO(nona): Makes focus out mode configuable, if necessary.
178 UpdateComposition(*composition_text_
, composition_cursor_
, true);
182 bool InputMethodEngine::ClearComposition(int context_id
,
183 std::string
* error
) {
185 *error
= kErrorNotActive
;
188 if (context_id
!= context_id_
|| context_id_
== -1) {
189 *error
= kErrorWrongContext
;
193 composition_cursor_
= 0;
194 composition_text_
.reset(new CompositionText());
195 UpdateComposition(*composition_text_
, composition_cursor_
, false);
199 bool InputMethodEngine::CommitText(int context_id
, const char* text
,
200 std::string
* error
) {
202 // TODO: Commit the text anyways.
203 *error
= kErrorNotActive
;
206 if (context_id
!= context_id_
|| context_id_
== -1) {
207 *error
= kErrorWrongContext
;
211 IMEBridge::Get()->GetInputContextHandler()->CommitText(text
);
215 bool InputMethodEngine::SendKeyEvents(
217 const std::vector
<KeyboardEvent
>& events
) {
221 // context_id == 0, means sending key events to non-input field.
222 // context_id_ == -1, means the focus is not in an input field.
223 if (context_id
!= 0 && (context_id
!= context_id_
|| context_id_
== -1)) {
227 ui::EventProcessor
* dispatcher
=
228 ash::Shell::GetPrimaryRootWindow()->GetHost()->event_processor();
230 for (size_t i
= 0; i
< events
.size(); ++i
) {
231 const KeyboardEvent
& event
= events
[i
];
232 const ui::EventType type
=
233 (event
.type
== "keyup") ? ui::ET_KEY_RELEASED
: ui::ET_KEY_PRESSED
;
235 // KeyboardCodeFromXKyeSym assumes US keyboard layout.
236 ui::KeycodeConverter
* conv
= ui::KeycodeConverter::GetInstance();
239 // DOM code (KeyA) -> XKB -> XKeySym (XK_A) -> KeyboardCode (VKEY_A)
240 const uint16 native_keycode
=
241 conv
->CodeToNativeKeycode(event
.code
.c_str());
242 const uint xkeysym
= ui::DefaultXKeysymFromHardwareKeycode(native_keycode
);
243 const ui::KeyboardCode key_code
= ui::KeyboardCodeFromXKeysym(xkeysym
);
245 const std::string code
= event
.code
;
246 int flags
= ui::EF_NONE
;
247 flags
|= event
.alt_key
? ui::EF_ALT_DOWN
: ui::EF_NONE
;
248 flags
|= event
.ctrl_key
? ui::EF_CONTROL_DOWN
: ui::EF_NONE
;
249 flags
|= event
.shift_key
? ui::EF_SHIFT_DOWN
: ui::EF_NONE
;
250 flags
|= event
.caps_lock
? ui::EF_CAPS_LOCK_DOWN
: ui::EF_NONE
;
252 ui::KeyEvent
ui_event(type
, key_code
, code
, flags
, false /* is_char */);
253 base::AutoReset
<const ui::KeyEvent
*> reset_sent_key(&sent_key_event_
,
255 ui::EventDispatchDetails details
= dispatcher
->OnEventFromSource(&ui_event
);
256 if (details
.dispatcher_destroyed
)
262 const InputMethodEngine::CandidateWindowProperty
&
263 InputMethodEngine::GetCandidateWindowProperty() const {
264 return candidate_window_property_
;
267 void InputMethodEngine::SetCandidateWindowProperty(
268 const CandidateWindowProperty
& property
) {
269 // Type conversion from InputMethodEngineInterface::CandidateWindowProperty to
270 // CandidateWindow::CandidateWindowProperty defined in chromeos/ime/.
271 ui::CandidateWindow::CandidateWindowProperty dest_property
;
272 dest_property
.page_size
= property
.page_size
;
273 dest_property
.is_cursor_visible
= property
.is_cursor_visible
;
274 dest_property
.is_vertical
= property
.is_vertical
;
275 dest_property
.show_window_at_composition
=
276 property
.show_window_at_composition
;
277 dest_property
.cursor_position
=
278 candidate_window_
->GetProperty().cursor_position
;
279 dest_property
.auxiliary_text
= property
.auxiliary_text
;
280 dest_property
.is_auxiliary_text_visible
= property
.is_auxiliary_text_visible
;
282 candidate_window_
->SetProperty(dest_property
);
283 candidate_window_property_
= property
;
286 IMECandidateWindowHandlerInterface
* cw_handler
=
287 IMEBridge::Get()->GetCandidateWindowHandler();
289 cw_handler
->UpdateLookupTable(*candidate_window_
, window_visible_
);
293 bool InputMethodEngine::SetCandidateWindowVisible(bool visible
,
294 std::string
* error
) {
296 *error
= kErrorNotActive
;
300 window_visible_
= visible
;
301 IMECandidateWindowHandlerInterface
* cw_handler
=
302 IMEBridge::Get()->GetCandidateWindowHandler();
304 cw_handler
->UpdateLookupTable(*candidate_window_
, window_visible_
);
308 bool InputMethodEngine::SetCandidates(
310 const std::vector
<Candidate
>& candidates
,
311 std::string
* error
) {
313 *error
= kErrorNotActive
;
316 if (context_id
!= context_id_
|| context_id_
== -1) {
317 *error
= kErrorWrongContext
;
321 // TODO: Nested candidates
322 candidate_ids_
.clear();
323 candidate_indexes_
.clear();
324 candidate_window_
->mutable_candidates()->clear();
325 for (std::vector
<Candidate
>::const_iterator ix
= candidates
.begin();
326 ix
!= candidates
.end(); ++ix
) {
327 ui::CandidateWindow::Entry entry
;
328 entry
.value
= base::UTF8ToUTF16(ix
->value
);
329 entry
.label
= base::UTF8ToUTF16(ix
->label
);
330 entry
.annotation
= base::UTF8ToUTF16(ix
->annotation
);
331 entry
.description_title
= base::UTF8ToUTF16(ix
->usage
.title
);
332 entry
.description_body
= base::UTF8ToUTF16(ix
->usage
.body
);
334 // Store a mapping from the user defined ID to the candidate index.
335 candidate_indexes_
[ix
->id
] = candidate_ids_
.size();
336 candidate_ids_
.push_back(ix
->id
);
338 candidate_window_
->mutable_candidates()->push_back(entry
);
341 IMECandidateWindowHandlerInterface
* cw_handler
=
342 IMEBridge::Get()->GetCandidateWindowHandler();
344 cw_handler
->UpdateLookupTable(*candidate_window_
, window_visible_
);
349 bool InputMethodEngine::SetCursorPosition(int context_id
, int candidate_id
,
350 std::string
* error
) {
352 *error
= kErrorNotActive
;
355 if (context_id
!= context_id_
|| context_id_
== -1) {
356 *error
= kErrorWrongContext
;
360 std::map
<int, int>::const_iterator position
=
361 candidate_indexes_
.find(candidate_id
);
362 if (position
== candidate_indexes_
.end()) {
363 *error
= kCandidateNotFound
;
367 candidate_window_
->set_cursor_position(position
->second
);
368 IMECandidateWindowHandlerInterface
* cw_handler
=
369 IMEBridge::Get()->GetCandidateWindowHandler();
371 cw_handler
->UpdateLookupTable(*candidate_window_
, window_visible_
);
375 bool InputMethodEngine::SetMenuItems(const std::vector
<MenuItem
>& items
) {
376 return UpdateMenuItems(items
);
379 bool InputMethodEngine::UpdateMenuItems(
380 const std::vector
<MenuItem
>& items
) {
384 ash::ime::InputMethodMenuItemList menu_item_list
;
385 for (std::vector
<MenuItem
>::const_iterator item
= items
.begin();
386 item
!= items
.end(); ++item
) {
387 ash::ime::InputMethodMenuItem property
;
388 MenuItemToProperty(*item
, &property
);
389 menu_item_list
.push_back(property
);
392 ash::ime::InputMethodMenuManager::GetInstance()->
393 SetCurrentInputMethodMenuItemList(
398 bool InputMethodEngine::IsActive() const {
402 void InputMethodEngine::KeyEventDone(input_method::KeyEventHandle
* key_data
,
404 KeyEventDoneCallback
* callback
=
405 reinterpret_cast<KeyEventDoneCallback
*>(key_data
);
406 callback
->Run(handled
);
410 bool InputMethodEngine::DeleteSurroundingText(int context_id
,
412 size_t number_of_chars
,
413 std::string
* error
) {
415 *error
= kErrorNotActive
;
418 if (context_id
!= context_id_
|| context_id_
== -1) {
419 *error
= kErrorWrongContext
;
423 if (offset
< 0 && static_cast<size_t>(-1 * offset
) != size_t(number_of_chars
))
424 return false; // Currently we can only support preceding text.
426 // TODO(nona): Return false if there is ongoing composition.
428 IMEInputContextHandlerInterface
* input_context
=
429 IMEBridge::Get()->GetInputContextHandler();
431 input_context
->DeleteSurroundingText(offset
, number_of_chars
);
436 void InputMethodEngine::HideInputView() {
437 keyboard::KeyboardController
* keyboard_controller
=
438 keyboard::KeyboardController::GetInstance();
439 if (keyboard_controller
) {
440 keyboard_controller
->HideKeyboard(
441 keyboard::KeyboardController::HIDE_REASON_MANUAL
);
445 void InputMethodEngine::EnableInputView(bool enabled
) {
446 const GURL
& url
= enabled
? input_view_url_
: GURL();
447 keyboard::SetOverrideContentUrl(url
);
448 keyboard::KeyboardController
* keyboard_controller
=
449 keyboard::KeyboardController::GetInstance();
450 if (keyboard_controller
)
451 keyboard_controller
->Reload();
454 void InputMethodEngine::FocusIn(
455 const IMEEngineHandlerInterface::InputContext
& input_context
) {
456 current_input_type_
= input_context
.type
;
460 // Prevent sending events on password field to 3rd-party IME extensions.
461 // And also make sure the VK fallback to system VK.
462 // TODO(shuchen): for password field, forcibly switch/lock the IME to the XKB
463 // keyboard related to the current IME.
464 if (current_input_type_
== ui::TEXT_INPUT_TYPE_PASSWORD
&&
465 !extension_ime_util::IsComponentExtensionIME(GetDescriptor().id())) {
466 EnableInputView(false);
470 context_id_
= next_context_id_
;
473 InputMethodEngineInterface::InputContext context
;
474 context
.id
= context_id_
;
475 switch (current_input_type_
) {
476 case ui::TEXT_INPUT_TYPE_SEARCH
:
477 context
.type
= "search";
479 case ui::TEXT_INPUT_TYPE_TELEPHONE
:
480 context
.type
= "tel";
482 case ui::TEXT_INPUT_TYPE_URL
:
483 context
.type
= "url";
485 case ui::TEXT_INPUT_TYPE_EMAIL
:
486 context
.type
= "email";
488 case ui::TEXT_INPUT_TYPE_NUMBER
:
489 context
.type
= "number";
491 case ui::TEXT_INPUT_TYPE_PASSWORD
:
492 context
.type
= "password";
495 context
.type
= "text";
499 observer_
->OnFocus(context
);
502 void InputMethodEngine::FocusOut() {
503 ui::TextInputType input_type
= current_input_type_
;
504 current_input_type_
= ui::TEXT_INPUT_TYPE_NONE
;
508 // Prevent sending events on password field to 3rd-party IME extensions.
509 // And also make sure the VK restore to IME input view.
510 if (input_type
== ui::TEXT_INPUT_TYPE_PASSWORD
&&
511 !extension_ime_util::IsComponentExtensionIME(GetDescriptor().id())) {
512 EnableInputView(true);
516 int context_id
= context_id_
;
518 observer_
->OnBlur(context_id
);
521 void InputMethodEngine::Enable() {
523 observer_
->OnActivate(engine_id_
);
524 current_input_type_
= IMEBridge::Get()->GetCurrentTextInputType();
525 FocusIn(IMEEngineHandlerInterface::InputContext(
526 current_input_type_
, ui::TEXT_INPUT_MODE_DEFAULT
));
527 EnableInputView(true);
530 void InputMethodEngine::Disable() {
532 observer_
->OnDeactivated(engine_id_
);
535 void InputMethodEngine::PropertyActivate(const std::string
& property_name
) {
536 observer_
->OnMenuItemActivated(engine_id_
, property_name
);
539 void InputMethodEngine::Reset() {
540 observer_
->OnReset(engine_id_
);
544 void GetExtensionKeyboardEventFromKeyEvent(
545 const ui::KeyEvent
& event
,
546 InputMethodEngine::KeyboardEvent
* ext_event
) {
547 DCHECK(event
.type() == ui::ET_KEY_RELEASED
||
548 event
.type() == ui::ET_KEY_PRESSED
);
550 ext_event
->type
= (event
.type() == ui::ET_KEY_RELEASED
) ? "keyup" : "keydown";
552 ext_event
->code
= event
.code();
553 ext_event
->alt_key
= event
.IsAltDown();
554 ext_event
->ctrl_key
= event
.IsControlDown();
555 ext_event
->shift_key
= event
.IsShiftDown();
556 ext_event
->caps_lock
= event
.IsCapsLockDown();
558 uint32 x11_keysym
= 0;
559 if (event
.HasNativeEvent()) {
560 const base::NativeEvent
& native_event
= event
.native_event();
561 DCHECK(native_event
);
563 XKeyEvent
* x_key
= &(static_cast<XEvent
*>(native_event
)->xkey
);
564 KeySym keysym
= NoSymbol
;
565 ::XLookupString(x_key
, NULL
, 0, &keysym
, NULL
);
568 // Convert ui::KeyEvent.key_code to DOM UIEvent key.
569 // XKeysymForWindowsKeyCode converts key_code to XKeySym, but it
570 // assumes US layout and does not care about CapLock state.
572 // TODO(komatsu): Support CapsLock states.
573 // TODO(komatsu): Support non-us keyboard layouts.
574 x11_keysym
= ui::XKeysymForWindowsKeyCode(event
.key_code(),
575 event
.IsShiftDown());
577 ext_event
->key
= ui::FromXKeycodeToKeyValue(x11_keysym
);
581 void InputMethodEngine::ProcessKeyEvent(
582 const ui::KeyEvent
& key_event
,
583 const KeyEventDoneCallback
& callback
) {
585 KeyEventDoneCallback
*handler
= new KeyEventDoneCallback();
588 KeyboardEvent ext_event
;
589 GetExtensionKeyboardEventFromKeyEvent(key_event
, &ext_event
);
591 // If the given key event is equal to the key event sent by
592 // SendKeyEvents, this engine ID is propagated to the extension IME.
593 // Note, this check relies on that ui::KeyEvent is propagated as
594 // reference without copying.
595 if (&key_event
== sent_key_event_
)
596 ext_event
.extension_id
= extension_id_
;
598 observer_
->OnKeyEvent(
601 reinterpret_cast<input_method::KeyEventHandle
*>(handler
));
604 void InputMethodEngine::CandidateClicked(uint32 index
) {
605 if (index
> candidate_ids_
.size()) {
609 // Only left button click is supported at this moment.
610 observer_
->OnCandidateClicked(
611 engine_id_
, candidate_ids_
.at(index
), MOUSE_BUTTON_LEFT
);
614 void InputMethodEngine::SetSurroundingText(const std::string
& text
,
617 observer_
->OnSurroundingTextChanged(engine_id_
,
619 static_cast<int>(cursor_pos
),
620 static_cast<int>(anchor_pos
));
623 // TODO(uekawa): rename this method to a more reasonable name.
624 void InputMethodEngine::MenuItemToProperty(
625 const MenuItem
& item
,
626 ash::ime::InputMethodMenuItem
* property
) {
627 property
->key
= item
.id
;
629 if (item
.modified
& MENU_ITEM_MODIFIED_LABEL
) {
630 property
->label
= item
.label
;
632 if (item
.modified
& MENU_ITEM_MODIFIED_VISIBLE
) {
633 // TODO(nona): Implement it.
635 if (item
.modified
& MENU_ITEM_MODIFIED_CHECKED
) {
636 property
->is_selection_item_checked
= item
.checked
;
638 if (item
.modified
& MENU_ITEM_MODIFIED_ENABLED
) {
639 // TODO(nona): implement sensitive entry(crbug.com/140192).
641 if (item
.modified
& MENU_ITEM_MODIFIED_STYLE
) {
642 if (!item
.children
.empty()) {
643 // TODO(nona): Implement it.
645 switch (item
.style
) {
646 case MENU_ITEM_STYLE_NONE
:
649 case MENU_ITEM_STYLE_CHECK
:
650 // TODO(nona): Implement it.
652 case MENU_ITEM_STYLE_RADIO
:
653 property
->is_selection_item
= true;
655 case MENU_ITEM_STYLE_SEPARATOR
:
656 // TODO(nona): Implement it.
662 // TODO(nona): Support item.children.
665 } // namespace chromeos