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()
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
->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 ash::Shell::GetInstance()->keyboard_controller();
439 if (keyboard_controller
) {
440 keyboard_controller
->HideKeyboard(
441 keyboard::KeyboardController::HIDE_REASON_MANUAL
);
445 void InputMethodEngine::FocusIn(
446 const IMEEngineHandlerInterface::InputContext
& input_context
) {
450 context_id_
= next_context_id_
;
453 InputMethodEngineInterface::InputContext context
;
454 context
.id
= context_id_
;
455 switch (input_context
.type
) {
456 case ui::TEXT_INPUT_TYPE_SEARCH
:
457 context
.type
= "search";
459 case ui::TEXT_INPUT_TYPE_TELEPHONE
:
460 context
.type
= "tel";
462 case ui::TEXT_INPUT_TYPE_URL
:
463 context
.type
= "url";
465 case ui::TEXT_INPUT_TYPE_EMAIL
:
466 context
.type
= "email";
468 case ui::TEXT_INPUT_TYPE_NUMBER
:
469 context
.type
= "number";
472 context
.type
= "text";
476 observer_
->OnFocus(context
);
479 void InputMethodEngine::FocusOut() {
483 int context_id
= context_id_
;
485 observer_
->OnBlur(context_id
);
488 void InputMethodEngine::Enable() {
490 observer_
->OnActivate(engine_id_
);
491 IMEEngineHandlerInterface::InputContext
context(ui::TEXT_INPUT_TYPE_TEXT
,
492 ui::TEXT_INPUT_MODE_DEFAULT
);
495 keyboard::SetOverrideContentUrl(input_view_url_
);
496 keyboard::KeyboardController
* keyboard_controller
=
497 ash::Shell::GetInstance()->keyboard_controller();
498 if (keyboard_controller
)
499 keyboard_controller
->Reload();
502 void InputMethodEngine::Disable() {
504 observer_
->OnDeactivated(engine_id_
);
507 keyboard::SetOverrideContentUrl(empty_url
);
508 keyboard::KeyboardController
* keyboard_controller
=
509 ash::Shell::GetInstance()->keyboard_controller();
510 if (keyboard_controller
)
511 keyboard_controller
->Reload();
514 void InputMethodEngine::PropertyActivate(const std::string
& property_name
) {
515 observer_
->OnMenuItemActivated(engine_id_
, property_name
);
518 void InputMethodEngine::Reset() {
519 observer_
->OnReset(engine_id_
);
523 void GetExtensionKeyboardEventFromKeyEvent(
524 const ui::KeyEvent
& event
,
525 InputMethodEngine::KeyboardEvent
* ext_event
) {
526 DCHECK(event
.type() == ui::ET_KEY_RELEASED
||
527 event
.type() == ui::ET_KEY_PRESSED
);
529 ext_event
->type
= (event
.type() == ui::ET_KEY_RELEASED
) ? "keyup" : "keydown";
531 ext_event
->code
= event
.code();
532 ext_event
->alt_key
= event
.IsAltDown();
533 ext_event
->ctrl_key
= event
.IsControlDown();
534 ext_event
->shift_key
= event
.IsShiftDown();
535 ext_event
->caps_lock
= event
.IsCapsLockDown();
537 uint32 x11_keysym
= 0;
538 if (event
.HasNativeEvent()) {
539 const base::NativeEvent
& native_event
= event
.native_event();
540 DCHECK(native_event
);
542 XKeyEvent
* x_key
= &(static_cast<XEvent
*>(native_event
)->xkey
);
543 KeySym keysym
= NoSymbol
;
544 ::XLookupString(x_key
, NULL
, 0, &keysym
, NULL
);
547 // Convert ui::KeyEvent.key_code to DOM UIEvent key.
548 // XKeysymForWindowsKeyCode converts key_code to XKeySym, but it
549 // assumes US layout and does not care about CapLock state.
551 // TODO(komatsu): Support CapsLock states.
552 // TODO(komatsu): Support non-us keyboard layouts.
553 x11_keysym
= ui::XKeysymForWindowsKeyCode(event
.key_code(),
554 event
.IsShiftDown());
556 ext_event
->key
= ui::FromXKeycodeToKeyValue(x11_keysym
);
560 void InputMethodEngine::ProcessKeyEvent(
561 const ui::KeyEvent
& key_event
,
562 const KeyEventDoneCallback
& callback
) {
564 KeyEventDoneCallback
*handler
= new KeyEventDoneCallback();
567 KeyboardEvent ext_event
;
568 GetExtensionKeyboardEventFromKeyEvent(key_event
, &ext_event
);
570 // If the given key event is equal to the key event sent by
571 // SendKeyEvents, this engine ID is propagated to the extension IME.
572 // Note, this check relies on that ui::KeyEvent is propagated as
573 // reference without copying.
574 if (&key_event
== sent_key_event_
)
575 ext_event
.extension_id
= extension_id_
;
577 observer_
->OnKeyEvent(
580 reinterpret_cast<input_method::KeyEventHandle
*>(handler
));
583 void InputMethodEngine::CandidateClicked(uint32 index
) {
584 if (index
> candidate_ids_
.size()) {
588 // Only left button click is supported at this moment.
589 observer_
->OnCandidateClicked(
590 engine_id_
, candidate_ids_
.at(index
), MOUSE_BUTTON_LEFT
);
593 void InputMethodEngine::SetSurroundingText(const std::string
& text
,
596 observer_
->OnSurroundingTextChanged(engine_id_
,
598 static_cast<int>(cursor_pos
),
599 static_cast<int>(anchor_pos
));
602 // TODO(uekawa): rename this method to a more reasonable name.
603 void InputMethodEngine::MenuItemToProperty(
604 const MenuItem
& item
,
605 ash::ime::InputMethodMenuItem
* property
) {
606 property
->key
= item
.id
;
608 if (item
.modified
& MENU_ITEM_MODIFIED_LABEL
) {
609 property
->label
= item
.label
;
611 if (item
.modified
& MENU_ITEM_MODIFIED_VISIBLE
) {
612 // TODO(nona): Implement it.
614 if (item
.modified
& MENU_ITEM_MODIFIED_CHECKED
) {
615 property
->is_selection_item_checked
= item
.checked
;
617 if (item
.modified
& MENU_ITEM_MODIFIED_ENABLED
) {
618 // TODO(nona): implement sensitive entry(crbug.com/140192).
620 if (item
.modified
& MENU_ITEM_MODIFIED_STYLE
) {
621 if (!item
.children
.empty()) {
622 // TODO(nona): Implement it.
624 switch (item
.style
) {
625 case MENU_ITEM_STYLE_NONE
:
628 case MENU_ITEM_STYLE_CHECK
:
629 // TODO(nona): Implement it.
631 case MENU_ITEM_STYLE_RADIO
:
632 property
->is_selection_item
= true;
634 case MENU_ITEM_STYLE_SEPARATOR
:
635 // TODO(nona): Implement it.
641 // TODO(nona): Support item.children.
644 } // namespace chromeos