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/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"
37 const char* kErrorNotActive = "IME is not active";
38 const char* kErrorWrongContext = "Context is not active";
39 const char* kCandidateNotFound = "Candidate not found";
43 // Notifies InputContextHandler that the composition is changed.
44 void UpdateComposition(const CompositionText& composition_text,
47 IBusInputContextHandlerInterface* input_context =
48 IMEBridge::Get()->GetInputContextHandler();
50 input_context->UpdateCompositionText(
51 composition_text, cursor_pos, is_visible);
56 InputMethodEngine::InputMethodEngine()
62 composition_text_(new CompositionText()),
63 composition_cursor_(0),
64 candidate_window_(new ui::CandidateWindow()),
65 window_visible_(false),
66 sent_key_event_(NULL) {}
68 InputMethodEngine::~InputMethodEngine() {
69 input_method::InputMethodManager::Get()->RemoveInputMethodExtension(imm_id_);
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.";
83 // TODO(komatsu): It is probably better to set observer out of Initialize.
85 engine_id_ = engine_id;
86 extension_id_ = extension_id;
88 input_method::InputMethodManager* manager =
89 input_method::InputMethodManager::Get();
90 ComponentExtensionIMEManager* comp_ext_ime_manager =
91 manager->GetComponentExtensionIMEManager();
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);
97 imm_id_ = extension_ime_util::GetInputMethodID(extension_id, engine_id);
100 input_view_url_ = input_view;
101 descriptor_ = input_method::InputMethodDescriptor(
104 std::string(), // TODO(uekawa): Set short name.
107 false, // is_login_keyboard
111 // TODO(komatsu): It is probably better to call AddInputMethodExtension
112 // out of Initialize.
113 manager->AddInputMethodExtension(imm_id_, this);
116 const input_method::InputMethodDescriptor& InputMethodEngine::GetDescriptor()
121 void InputMethodEngine::StartIme() {
122 input_method::InputMethodManager* manager =
123 input_method::InputMethodManager::Get();
124 if (manager && imm_id_ == manager->GetCurrentInputMethod().id())
128 bool InputMethodEngine::SetComposition(
134 const std::vector<SegmentInfo>& segments,
135 std::string* error) {
137 *error = kErrorNotActive;
140 if (context_id != context_id_ || context_id_ == -1) {
141 *error = kErrorWrongContext;
145 composition_cursor_ = cursor;
146 composition_text_.reset(new CompositionText());
147 composition_text_->set_text(base::UTF8ToUTF16(text));
149 composition_text_->set_selection_start(selection_start);
150 composition_text_->set_selection_end(selection_end);
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;
157 switch (segment->style) {
158 case SEGMENT_STYLE_UNDERLINE:
159 underline.type = CompositionText::COMPOSITION_TEXT_UNDERLINE_SINGLE;
161 case SEGMENT_STYLE_DOUBLE_UNDERLINE:
162 underline.type = CompositionText::COMPOSITION_TEXT_UNDERLINE_DOUBLE;
168 underline.start_index = segment->start;
169 underline.end_index = segment->end;
170 composition_text_->mutable_underline_attributes()->push_back(underline);
173 // TODO(nona): Makes focus out mode configuable, if necessary.
174 UpdateComposition(*composition_text_, composition_cursor_, true);
178 bool InputMethodEngine::ClearComposition(int context_id,
179 std::string* error) {
181 *error = kErrorNotActive;
184 if (context_id != context_id_ || context_id_ == -1) {
185 *error = kErrorWrongContext;
189 composition_cursor_ = 0;
190 composition_text_.reset(new CompositionText());
191 UpdateComposition(*composition_text_, composition_cursor_, false);
195 bool InputMethodEngine::CommitText(int context_id, const char* text,
196 std::string* error) {
198 // TODO: Commit the text anyways.
199 *error = kErrorNotActive;
202 if (context_id != context_id_ || context_id_ == -1) {
203 *error = kErrorWrongContext;
207 IMEBridge::Get()->GetInputContextHandler()->CommitText(text);
211 bool InputMethodEngine::SendKeyEvents(
213 const std::vector<KeyboardEvent>& events) {
217 if (context_id != context_id_ || context_id_ == -1) {
221 aura::WindowEventDispatcher* dispatcher =
222 ash::Shell::GetPrimaryRootWindow()->GetDispatcher();
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;
229 // KeyboardCodeFromXKyeSym assumes US keyboard layout.
230 ui::KeycodeConverter* conv = ui::KeycodeConverter::GetInstance();
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);
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;
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_,
249 ui::EventDispatchDetails details = dispatcher->OnEventFromSource(&ui_event);
250 if (details.dispatcher_destroyed)
256 const InputMethodEngine::CandidateWindowProperty&
257 InputMethodEngine::GetCandidateWindowProperty() const {
258 return candidate_window_property_;
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;
276 candidate_window_->SetProperty(dest_property);
277 candidate_window_property_ = property;
280 IBusPanelCandidateWindowHandlerInterface* cw_handler =
281 IMEBridge::Get()->GetCandidateWindowHandler();
283 cw_handler->UpdateLookupTable(*candidate_window_, window_visible_);
287 bool InputMethodEngine::SetCandidateWindowVisible(bool visible,
288 std::string* error) {
290 *error = kErrorNotActive;
294 window_visible_ = visible;
295 IBusPanelCandidateWindowHandlerInterface* cw_handler =
296 IMEBridge::Get()->GetCandidateWindowHandler();
298 cw_handler->UpdateLookupTable(*candidate_window_, window_visible_);
302 bool InputMethodEngine::SetCandidates(
304 const std::vector<Candidate>& candidates,
305 std::string* error) {
307 *error = kErrorNotActive;
310 if (context_id != context_id_ || context_id_ == -1) {
311 *error = kErrorWrongContext;
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);
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);
332 candidate_window_->mutable_candidates()->push_back(entry);
335 IBusPanelCandidateWindowHandlerInterface* cw_handler =
336 IMEBridge::Get()->GetCandidateWindowHandler();
338 cw_handler->UpdateLookupTable(*candidate_window_, window_visible_);
343 bool InputMethodEngine::SetCursorPosition(int context_id, int candidate_id,
344 std::string* error) {
346 *error = kErrorNotActive;
349 if (context_id != context_id_ || context_id_ == -1) {
350 *error = kErrorWrongContext;
354 std::map<int, int>::const_iterator position =
355 candidate_indexes_.find(candidate_id);
356 if (position == candidate_indexes_.end()) {
357 *error = kCandidateNotFound;
361 candidate_window_->set_cursor_position(position->second);
362 IBusPanelCandidateWindowHandlerInterface* cw_handler =
363 IMEBridge::Get()->GetCandidateWindowHandler();
365 cw_handler->UpdateLookupTable(*candidate_window_, window_visible_);
369 bool InputMethodEngine::SetMenuItems(const std::vector<MenuItem>& items) {
370 return UpdateMenuItems(items);
373 bool InputMethodEngine::UpdateMenuItems(
374 const std::vector<MenuItem>& items) {
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);
386 input_method::InputMethodManager* manager =
387 input_method::InputMethodManager::Get();
389 manager->SetCurrentInputMethodProperties(property_list);
394 bool InputMethodEngine::IsActive() const {
398 void InputMethodEngine::KeyEventDone(input_method::KeyEventHandle* key_data,
400 KeyEventDoneCallback* callback =
401 reinterpret_cast<KeyEventDoneCallback*>(key_data);
402 callback->Run(handled);
406 bool InputMethodEngine::DeleteSurroundingText(int context_id,
408 size_t number_of_chars,
409 std::string* error) {
411 *error = kErrorNotActive;
414 if (context_id != context_id_ || context_id_ == -1) {
415 *error = kErrorWrongContext;
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.
422 // TODO(nona): Return false if there is ongoing composition.
424 IBusInputContextHandlerInterface* input_context =
425 IMEBridge::Get()->GetInputContextHandler();
427 input_context->DeleteSurroundingText(offset, number_of_chars);
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);
441 void InputMethodEngine::FocusIn(
442 const IMEEngineHandlerInterface::InputContext& input_context) {
446 context_id_ = next_context_id_;
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";
455 case ui::TEXT_INPUT_TYPE_TELEPHONE:
456 context.type = "tel";
458 case ui::TEXT_INPUT_TYPE_URL:
459 context.type = "url";
461 case ui::TEXT_INPUT_TYPE_EMAIL:
462 context.type = "email";
464 case ui::TEXT_INPUT_TYPE_NUMBER:
465 context.type = "number";
468 context.type = "text";
472 observer_->OnFocus(context);
475 void InputMethodEngine::FocusOut() {
479 int context_id = context_id_;
481 observer_->OnBlur(context_id);
484 void InputMethodEngine::Enable() {
486 observer_->OnActivate(engine_id_);
487 IMEEngineHandlerInterface::InputContext context(ui::TEXT_INPUT_TYPE_TEXT,
488 ui::TEXT_INPUT_MODE_DEFAULT);
491 keyboard::KeyboardController* keyboard_controller =
492 ash::Shell::GetInstance()->keyboard_controller();
493 if (keyboard_controller) {
494 keyboard_controller->SetOverrideContentUrl(input_view_url_);
498 void InputMethodEngine::Disable() {
500 observer_->OnDeactivated(engine_id_);
502 keyboard::KeyboardController* keyboard_controller =
503 ash::Shell::GetInstance()->keyboard_controller();
504 if (keyboard_controller) {
506 keyboard_controller->SetOverrideContentUrl(empty_url);
510 void InputMethodEngine::PropertyActivate(const std::string& property_name) {
511 observer_->OnMenuItemActivated(engine_id_, property_name);
514 void InputMethodEngine::Reset() {
515 observer_->OnReset(engine_id_);
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);
525 ext_event->type = (event.type() == ui::ET_KEY_RELEASED) ? "keyup" : "keydown";
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();
533 uint32 ibus_keyval = 0;
534 if (event.HasNativeEvent()) {
535 const base::NativeEvent& native_event = event.native_event();
536 DCHECK(native_event);
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;
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.
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());
552 ext_event->key = input_method::GetIBusKey(ibus_keyval);
556 void InputMethodEngine::ProcessKeyEvent(
557 const ui::KeyEvent& key_event,
558 const KeyEventDoneCallback& callback) {
560 KeyEventDoneCallback *handler = new KeyEventDoneCallback();
563 KeyboardEvent ext_event;
564 GetExtensionKeyboardEventFromKeyEvent(key_event, &ext_event);
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_;
573 observer_->OnKeyEvent(
576 reinterpret_cast<input_method::KeyEventHandle*>(handler));
579 void InputMethodEngine::CandidateClicked(uint32 index) {
580 if (index > candidate_ids_.size()) {
584 // Only left button click is supported at this moment.
585 observer_->OnCandidateClicked(
586 engine_id_, candidate_ids_.at(index), MOUSE_BUTTON_LEFT);
589 void InputMethodEngine::SetSurroundingText(const std::string& text,
592 observer_->OnSurroundingTextChanged(engine_id_,
594 static_cast<int>(cursor_pos),
595 static_cast<int>(anchor_pos));
598 void InputMethodEngine::MenuItemToProperty(
599 const MenuItem& item,
600 input_method::InputMethodProperty* property) {
601 property->key = item.id;
603 if (item.modified & MENU_ITEM_MODIFIED_LABEL) {
604 property->label = item.label;
606 if (item.modified & MENU_ITEM_MODIFIED_VISIBLE) {
607 // TODO(nona): Implement it.
609 if (item.modified & MENU_ITEM_MODIFIED_CHECKED) {
610 property->is_selection_item_checked = item.checked;
612 if (item.modified & MENU_ITEM_MODIFIED_ENABLED) {
613 // TODO(nona): implement sensitive entry(crbug.com/140192).
615 if (item.modified & MENU_ITEM_MODIFIED_STYLE) {
616 if (!item.children.empty()) {
617 // TODO(nona): Implement it.
619 switch (item.style) {
620 case MENU_ITEM_STYLE_NONE:
623 case MENU_ITEM_STYLE_CHECK:
624 // TODO(nona): Implement it.
626 case MENU_ITEM_STYLE_RADIO:
627 property->is_selection_item = true;
629 case MENU_ITEM_STYLE_SEPARATOR:
630 // TODO(nona): Implement it.
636 // TODO(nona): Support item.children.
639 } // namespace chromeos