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/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/extension_ime_util.h"
25 #include "chromeos/ime/ibus_keymap.h"
26 #include "chromeos/ime/ibus_text.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"
37 const char* kErrorNotActive
= "IME is not active";
38 const char* kErrorWrongContext
= "Context is not active";
39 const char* kCandidateNotFound
= "Candidate not found";
40 const char* kEngineBusPrefix
= "org.freedesktop.IBus.";
44 // Notifies InputContextHandler that the preedit is changed.
45 void UpdatePreedit(const IBusText
& ibus_text
,
48 IBusInputContextHandlerInterface
* input_context
=
49 IBusBridge::Get()->GetInputContextHandler();
51 input_context
->UpdatePreeditText(ibus_text
, cursor_pos
, is_visible
);
56 InputMethodEngine::InputMethodEngine()
62 preedit_text_(new IBusText()),
64 candidate_window_(new ui::CandidateWindow()),
65 window_visible_(false) {}
67 InputMethodEngine::~InputMethodEngine() {
68 input_method::InputMethodManager::Get()->RemoveInputMethodExtension(imm_id_
);
71 void InputMethodEngine::Initialize(
72 InputMethodEngineInterface::Observer
* observer
,
73 const char* engine_name
,
74 const char* extension_id
,
75 const char* engine_id
,
76 const std::vector
<std::string
>& languages
,
77 const std::vector
<std::string
>& layouts
,
78 const GURL
& options_page
,
79 const GURL
& input_view
) {
80 DCHECK(observer
) << "Observer must not be null.";
82 // TODO(komatsu): It is probably better to set observer out of Initialize.
84 engine_id_
= engine_id
;
86 input_method::InputMethodManager
* manager
=
87 input_method::InputMethodManager::Get();
88 ComponentExtensionIMEManager
* comp_ext_ime_manager
=
89 manager
->GetComponentExtensionIMEManager();
91 if (comp_ext_ime_manager
->IsInitialized() &&
92 comp_ext_ime_manager
->IsWhitelistedExtension(extension_id
)) {
93 imm_id_
= comp_ext_ime_manager
->GetId(extension_id
, engine_id
);
95 imm_id_
= extension_ime_util::GetInputMethodID(extension_id
, engine_id
);
98 input_view_url_
= input_view
;
99 descriptor_
= input_method::InputMethodDescriptor(imm_id_
,
103 false, // is_login_keyboard
107 // TODO(komatsu): It is probably better to call AddInputMethodExtension
108 // out of Initialize.
109 manager
->AddInputMethodExtension(imm_id_
, this);
112 const input_method::InputMethodDescriptor
& InputMethodEngine::GetDescriptor()
117 void InputMethodEngine::StartIme() {
118 input_method::InputMethodManager
* manager
=
119 input_method::InputMethodManager::Get();
120 if (manager
&& imm_id_
== manager
->GetCurrentInputMethod().id())
124 bool InputMethodEngine::SetComposition(
130 const std::vector
<SegmentInfo
>& segments
,
131 std::string
* error
) {
133 *error
= kErrorNotActive
;
136 if (context_id
!= context_id_
|| context_id_
== -1) {
137 *error
= kErrorWrongContext
;
141 preedit_cursor_
= cursor
;
142 preedit_text_
.reset(new IBusText());
143 preedit_text_
->set_text(text
);
145 preedit_text_
->set_selection_start(selection_start
);
146 preedit_text_
->set_selection_end(selection_end
);
148 // TODO: Add support for displaying selected text in the composition string.
149 for (std::vector
<SegmentInfo
>::const_iterator segment
= segments
.begin();
150 segment
!= segments
.end(); ++segment
) {
151 IBusText::UnderlineAttribute underline
;
153 switch (segment
->style
) {
154 case SEGMENT_STYLE_UNDERLINE
:
155 underline
.type
= IBusText::IBUS_TEXT_UNDERLINE_SINGLE
;
157 case SEGMENT_STYLE_DOUBLE_UNDERLINE
:
158 underline
.type
= IBusText::IBUS_TEXT_UNDERLINE_DOUBLE
;
164 underline
.start_index
= segment
->start
;
165 underline
.end_index
= segment
->end
;
166 preedit_text_
->mutable_underline_attributes()->push_back(underline
);
169 // TODO(nona): Makes focus out mode configuable, if necessary.
170 UpdatePreedit(*preedit_text_
, preedit_cursor_
, true);
174 bool InputMethodEngine::ClearComposition(int context_id
,
175 std::string
* error
) {
177 *error
= kErrorNotActive
;
180 if (context_id
!= context_id_
|| context_id_
== -1) {
181 *error
= kErrorWrongContext
;
186 preedit_text_
.reset(new IBusText());
187 UpdatePreedit(*preedit_text_
, preedit_cursor_
, false);
191 bool InputMethodEngine::CommitText(int context_id
, const char* text
,
192 std::string
* error
) {
194 // TODO: Commit the text anyways.
195 *error
= kErrorNotActive
;
198 if (context_id
!= context_id_
|| context_id_
== -1) {
199 *error
= kErrorWrongContext
;
203 IBusBridge::Get()->GetInputContextHandler()->CommitText(text
);
207 bool InputMethodEngine::SendKeyEvents(
209 const std::vector
<KeyboardEvent
>& events
) {
213 if (context_id
!= context_id_
|| context_id_
== -1) {
217 aura::WindowEventDispatcher
* dispatcher
=
218 ash::Shell::GetPrimaryRootWindow()->GetDispatcher();
220 for (size_t i
= 0; i
< events
.size(); ++i
) {
221 const KeyboardEvent
& event
= events
[i
];
222 const ui::EventType type
=
223 (event
.type
== "keyup") ? ui::ET_KEY_RELEASED
: ui::ET_KEY_PRESSED
;
225 // KeyboardCodeFromXKyeSym assumes US keyboard layout.
226 ui::KeycodeConverter
* conv
= ui::KeycodeConverter::GetInstance();
229 // DOM code (KeyA) -> XKB -> XKeySym (XK_A) -> KeyboardCode (VKEY_A)
230 const uint16 native_keycode
=
231 conv
->CodeToNativeKeycode(event
.code
.c_str());
232 const uint xkeysym
= ui::DefaultXKeysymFromHardwareKeycode(native_keycode
);
233 const ui::KeyboardCode key_code
= ui::KeyboardCodeFromXKeysym(xkeysym
);
235 const std::string code
= event
.code
;
236 int flags
= ui::EF_NONE
;
237 flags
|= event
.alt_key
? ui::EF_ALT_DOWN
: ui::EF_NONE
;
238 flags
|= event
.ctrl_key
? ui::EF_CONTROL_DOWN
: ui::EF_NONE
;
239 flags
|= event
.shift_key
? ui::EF_SHIFT_DOWN
: ui::EF_NONE
;
240 flags
|= event
.caps_lock
? ui::EF_CAPS_LOCK_DOWN
: ui::EF_NONE
;
242 ui::KeyEvent
ui_event(type
, key_code
, code
, flags
, false /* is_char */);
243 dispatcher
->AsWindowTreeHostDelegate()->OnHostKeyEvent(&ui_event
);
248 const InputMethodEngine::CandidateWindowProperty
&
249 InputMethodEngine::GetCandidateWindowProperty() const {
250 return candidate_window_property_
;
253 void InputMethodEngine::SetCandidateWindowProperty(
254 const CandidateWindowProperty
& property
) {
255 // Type conversion from InputMethodEngineInterface::CandidateWindowProperty to
256 // CandidateWindow::CandidateWindowProperty defined in chromeos/ime/.
257 ui::CandidateWindow::CandidateWindowProperty dest_property
;
258 dest_property
.page_size
= property
.page_size
;
259 dest_property
.is_cursor_visible
= property
.is_cursor_visible
;
260 dest_property
.is_vertical
= property
.is_vertical
;
261 dest_property
.show_window_at_composition
=
262 property
.show_window_at_composition
;
263 dest_property
.cursor_position
=
264 candidate_window_
->GetProperty().cursor_position
;
265 dest_property
.auxiliary_text
= property
.auxiliary_text
;
266 dest_property
.is_auxiliary_text_visible
= property
.is_auxiliary_text_visible
;
268 candidate_window_
->SetProperty(dest_property
);
269 candidate_window_property_
= property
;
272 IBusPanelCandidateWindowHandlerInterface
* cw_handler
=
273 IBusBridge::Get()->GetCandidateWindowHandler();
275 cw_handler
->UpdateLookupTable(*candidate_window_
, window_visible_
);
279 bool InputMethodEngine::SetCandidateWindowVisible(bool visible
,
280 std::string
* error
) {
282 *error
= kErrorNotActive
;
286 window_visible_
= visible
;
287 IBusPanelCandidateWindowHandlerInterface
* cw_handler
=
288 IBusBridge::Get()->GetCandidateWindowHandler();
290 cw_handler
->UpdateLookupTable(*candidate_window_
, window_visible_
);
294 bool InputMethodEngine::SetCandidates(
296 const std::vector
<Candidate
>& candidates
,
297 std::string
* error
) {
299 *error
= kErrorNotActive
;
302 if (context_id
!= context_id_
|| context_id_
== -1) {
303 *error
= kErrorWrongContext
;
307 // TODO: Nested candidates
308 candidate_ids_
.clear();
309 candidate_indexes_
.clear();
310 candidate_window_
->mutable_candidates()->clear();
311 for (std::vector
<Candidate
>::const_iterator ix
= candidates
.begin();
312 ix
!= candidates
.end(); ++ix
) {
313 ui::CandidateWindow::Entry entry
;
314 entry
.value
= ix
->value
;
315 entry
.label
= ix
->label
;
316 entry
.annotation
= ix
->annotation
;
317 entry
.description_title
= ix
->usage
.title
;
318 entry
.description_body
= ix
->usage
.body
;
320 // Store a mapping from the user defined ID to the candidate index.
321 candidate_indexes_
[ix
->id
] = candidate_ids_
.size();
322 candidate_ids_
.push_back(ix
->id
);
324 candidate_window_
->mutable_candidates()->push_back(entry
);
327 IBusPanelCandidateWindowHandlerInterface
* cw_handler
=
328 IBusBridge::Get()->GetCandidateWindowHandler();
330 cw_handler
->UpdateLookupTable(*candidate_window_
, window_visible_
);
335 bool InputMethodEngine::SetCursorPosition(int context_id
, int candidate_id
,
336 std::string
* error
) {
338 *error
= kErrorNotActive
;
341 if (context_id
!= context_id_
|| context_id_
== -1) {
342 *error
= kErrorWrongContext
;
346 std::map
<int, int>::const_iterator position
=
347 candidate_indexes_
.find(candidate_id
);
348 if (position
== candidate_indexes_
.end()) {
349 *error
= kCandidateNotFound
;
353 candidate_window_
->set_cursor_position(position
->second
);
354 IBusPanelCandidateWindowHandlerInterface
* cw_handler
=
355 IBusBridge::Get()->GetCandidateWindowHandler();
357 cw_handler
->UpdateLookupTable(*candidate_window_
, window_visible_
);
361 bool InputMethodEngine::SetMenuItems(const std::vector
<MenuItem
>& items
) {
362 return UpdateMenuItems(items
);
365 bool InputMethodEngine::UpdateMenuItems(
366 const std::vector
<MenuItem
>& items
) {
370 input_method::InputMethodPropertyList property_list
;
371 for (std::vector
<MenuItem
>::const_iterator item
= items
.begin();
372 item
!= items
.end(); ++item
) {
373 input_method::InputMethodProperty property
;
374 MenuItemToProperty(*item
, &property
);
375 property_list
.push_back(property
);
378 input_method::InputMethodManager
* manager
=
379 input_method::InputMethodManager::Get();
381 manager
->SetCurrentInputMethodProperties(property_list
);
386 bool InputMethodEngine::IsActive() const {
390 void InputMethodEngine::KeyEventDone(input_method::KeyEventHandle
* key_data
,
392 KeyEventDoneCallback
* callback
=
393 reinterpret_cast<KeyEventDoneCallback
*>(key_data
);
394 callback
->Run(handled
);
398 bool InputMethodEngine::DeleteSurroundingText(int context_id
,
400 size_t number_of_chars
,
401 std::string
* error
) {
403 *error
= kErrorNotActive
;
406 if (context_id
!= context_id_
|| context_id_
== -1) {
407 *error
= kErrorWrongContext
;
411 if (offset
< 0 && static_cast<size_t>(-1 * offset
) != size_t(number_of_chars
))
412 return false; // Currently we can only support preceding text.
414 // TODO(nona): Return false if there is ongoing composition.
416 IBusInputContextHandlerInterface
* input_context
=
417 IBusBridge::Get()->GetInputContextHandler();
419 input_context
->DeleteSurroundingText(offset
, number_of_chars
);
424 void InputMethodEngine::HideInputView() {
425 keyboard::KeyboardController
* keyboard_controller
=
426 ash::Shell::GetInstance()->keyboard_controller();
427 if (keyboard_controller
) {
428 keyboard_controller
->HideKeyboard(
429 keyboard::KeyboardController::HIDE_REASON_MANUAL
);
433 void InputMethodEngine::FocusIn(
434 const IBusEngineHandlerInterface::InputContext
& input_context
) {
438 context_id_
= next_context_id_
;
441 InputMethodEngineInterface::InputContext context
;
442 context
.id
= context_id_
;
443 switch (input_context
.type
) {
444 case ui::TEXT_INPUT_TYPE_SEARCH
:
445 context
.type
= "search";
447 case ui::TEXT_INPUT_TYPE_TELEPHONE
:
448 context
.type
= "tel";
450 case ui::TEXT_INPUT_TYPE_URL
:
451 context
.type
= "url";
453 case ui::TEXT_INPUT_TYPE_EMAIL
:
454 context
.type
= "email";
456 case ui::TEXT_INPUT_TYPE_NUMBER
:
457 context
.type
= "number";
460 context
.type
= "text";
464 observer_
->OnFocus(context
);
467 void InputMethodEngine::FocusOut() {
471 int context_id
= context_id_
;
473 observer_
->OnBlur(context_id
);
476 void InputMethodEngine::Enable() {
478 observer_
->OnActivate(engine_id_
);
479 IBusEngineHandlerInterface::InputContext
context(ui::TEXT_INPUT_TYPE_TEXT
,
480 ui::TEXT_INPUT_MODE_DEFAULT
);
483 keyboard::KeyboardController
* keyboard_controller
=
484 ash::Shell::GetInstance()->keyboard_controller();
485 if (keyboard_controller
) {
486 keyboard_controller
->SetOverrideContentUrl(input_view_url_
);
490 void InputMethodEngine::Disable() {
492 observer_
->OnDeactivated(engine_id_
);
494 keyboard::KeyboardController
* keyboard_controller
=
495 ash::Shell::GetInstance()->keyboard_controller();
496 if (keyboard_controller
) {
498 keyboard_controller
->SetOverrideContentUrl(empty_url
);
502 void InputMethodEngine::PropertyActivate(const std::string
& property_name
) {
503 observer_
->OnMenuItemActivated(engine_id_
, property_name
);
506 void InputMethodEngine::Reset() {
507 observer_
->OnReset(engine_id_
);
511 void GetExtensionKeyboardEventFromKeyEvent(
512 const ui::KeyEvent
& event
,
513 InputMethodEngine::KeyboardEvent
* ext_event
) {
514 DCHECK(event
.type() == ui::ET_KEY_RELEASED
||
515 event
.type() == ui::ET_KEY_PRESSED
);
517 ext_event
->type
= (event
.type() == ui::ET_KEY_RELEASED
) ? "keyup" : "keydown";
519 ext_event
->code
= event
.code();
520 ext_event
->alt_key
= event
.IsAltDown();
521 ext_event
->ctrl_key
= event
.IsControlDown();
522 ext_event
->shift_key
= event
.IsShiftDown();
523 ext_event
->caps_lock
= event
.IsCapsLockDown();
525 uint32 ibus_keyval
= 0;
526 if (event
.HasNativeEvent()) {
527 const base::NativeEvent
& native_event
= event
.native_event();
528 DCHECK(native_event
);
530 XKeyEvent
* x_key
= &(static_cast<XEvent
*>(native_event
)->xkey
);
531 KeySym keysym
= NoSymbol
;
532 ::XLookupString(x_key
, NULL
, 0, &keysym
, NULL
);
533 ibus_keyval
= keysym
;
535 // Convert ui::KeyEvent.key_code to DOM UIEvent key.
536 // XKeysymForWindowsKeyCode converts key_code to XKeySym, but it
537 // assumes US layout and does not care about CapLock state.
539 // TODO(komatsu): Support CapsLock states.
540 // TODO(komatsu): Support non-us keyboard layouts.
541 ibus_keyval
= ui::XKeysymForWindowsKeyCode(event
.key_code(),
542 event
.IsShiftDown());
544 ext_event
->key
= input_method::GetIBusKey(ibus_keyval
);
548 void InputMethodEngine::ProcessKeyEvent(
549 const ui::KeyEvent
& key_event
,
550 const KeyEventDoneCallback
& callback
) {
552 KeyEventDoneCallback
*handler
= new KeyEventDoneCallback();
555 KeyboardEvent ext_event
;
556 GetExtensionKeyboardEventFromKeyEvent(key_event
, &ext_event
);
557 observer_
->OnKeyEvent(
560 reinterpret_cast<input_method::KeyEventHandle
*>(handler
));
563 void InputMethodEngine::CandidateClicked(uint32 index
) {
564 if (index
> candidate_ids_
.size()) {
568 // Only left button click is supported at this moment.
569 observer_
->OnCandidateClicked(
570 engine_id_
, candidate_ids_
.at(index
), MOUSE_BUTTON_LEFT
);
573 void InputMethodEngine::SetSurroundingText(const std::string
& text
,
576 observer_
->OnSurroundingTextChanged(engine_id_
,
578 static_cast<int>(cursor_pos
),
579 static_cast<int>(anchor_pos
));
582 void InputMethodEngine::MenuItemToProperty(
583 const MenuItem
& item
,
584 input_method::InputMethodProperty
* property
) {
585 property
->key
= item
.id
;
587 if (item
.modified
& MENU_ITEM_MODIFIED_LABEL
) {
588 property
->label
= item
.label
;
590 if (item
.modified
& MENU_ITEM_MODIFIED_VISIBLE
) {
591 // TODO(nona): Implement it.
593 if (item
.modified
& MENU_ITEM_MODIFIED_CHECKED
) {
594 property
->is_selection_item_checked
= item
.checked
;
596 if (item
.modified
& MENU_ITEM_MODIFIED_ENABLED
) {
597 // TODO(nona): implement sensitive entry(crbug.com/140192).
599 if (item
.modified
& MENU_ITEM_MODIFIED_STYLE
) {
600 if (!item
.children
.empty()) {
601 // TODO(nona): Implement it.
603 switch (item
.style
) {
604 case MENU_ITEM_STYLE_NONE
:
607 case MENU_ITEM_STYLE_CHECK
:
608 // TODO(nona): Implement it.
610 case MENU_ITEM_STYLE_RADIO
:
611 property
->is_selection_item
= true;
613 case MENU_ITEM_STYLE_SEPARATOR
:
614 // TODO(nona): Implement it.
620 // TODO(nona): Support item.children.
623 } // namespace chromeos