Cast: Stop logging kVideoFrameSentToEncoder and rename a couple events.
[chromium-blink-merge.git] / chrome / browser / chromeos / input_method / input_method_engine.cc
blobaaa9539448581c3f58050196007d00d9fa5ebdbc
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"
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>
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"
40 namespace chromeos {
41 const char* kErrorNotActive = "IME is not active";
42 const char* kErrorWrongContext = "Context is not active";
43 const char* kCandidateNotFound = "Candidate not found";
45 namespace {
47 // Notifies InputContextHandler that the composition is changed.
48 void UpdateComposition(const CompositionText& composition_text,
49 uint32 cursor_pos,
50 bool is_visible) {
51 IMEInputContextHandlerInterface* input_context =
52 IMEBridge::Get()->GetInputContextHandler();
53 if (input_context)
54 input_context->UpdateCompositionText(
55 composition_text, cursor_pos, is_visible);
58 } // namespace
60 InputMethodEngine::InputMethodEngine()
61 : current_input_type_(ui::TEXT_INPUT_TYPE_NONE),
62 active_(false),
63 context_id_(0),
64 next_context_id_(1),
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);
99 } else {
100 imm_id_ = extension_ime_util::GetInputMethodID(extension_id, engine_id);
103 input_view_url_ = input_view;
104 descriptor_ = input_method::InputMethodDescriptor(
105 imm_id_,
106 engine_name,
107 std::string(), // TODO(uekawa): Set short name.
108 layouts,
109 languages,
110 extension_ime_util::IsKeyboardLayoutExtension(
111 imm_id_), // is_login_keyboard
112 options_page,
113 input_view);
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()
121 const {
122 return descriptor_;
125 void InputMethodEngine::NotifyImeReady() {
126 input_method::InputMethodManager* manager =
127 input_method::InputMethodManager::Get();
128 if (manager && imm_id_ == manager->GetCurrentInputMethod().id())
129 Enable();
132 bool InputMethodEngine::SetComposition(
133 int context_id,
134 const char* text,
135 int selection_start,
136 int selection_end,
137 int cursor,
138 const std::vector<SegmentInfo>& segments,
139 std::string* error) {
140 if (!active_) {
141 *error = kErrorNotActive;
142 return false;
144 if (context_id != context_id_ || context_id_ == -1) {
145 *error = kErrorWrongContext;
146 return false;
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;
164 break;
165 case SEGMENT_STYLE_DOUBLE_UNDERLINE:
166 underline.type = CompositionText::COMPOSITION_TEXT_UNDERLINE_DOUBLE;
167 break;
168 default:
169 continue;
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);
179 return true;
182 bool InputMethodEngine::ClearComposition(int context_id,
183 std::string* error) {
184 if (!active_) {
185 *error = kErrorNotActive;
186 return false;
188 if (context_id != context_id_ || context_id_ == -1) {
189 *error = kErrorWrongContext;
190 return false;
193 composition_cursor_ = 0;
194 composition_text_.reset(new CompositionText());
195 UpdateComposition(*composition_text_, composition_cursor_, false);
196 return true;
199 bool InputMethodEngine::CommitText(int context_id, const char* text,
200 std::string* error) {
201 if (!active_) {
202 // TODO: Commit the text anyways.
203 *error = kErrorNotActive;
204 return false;
206 if (context_id != context_id_ || context_id_ == -1) {
207 *error = kErrorWrongContext;
208 return false;
211 IMEBridge::Get()->GetInputContextHandler()->CommitText(text);
212 return true;
215 bool InputMethodEngine::SendKeyEvents(
216 int context_id,
217 const std::vector<KeyboardEvent>& events) {
218 if (!active_) {
219 return false;
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)) {
224 return false;
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();
237 DCHECK(conv);
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_,
254 &ui_event);
255 ui::EventDispatchDetails details = dispatcher->OnEventFromSource(&ui_event);
256 if (details.dispatcher_destroyed)
257 break;
259 return true;
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;
285 if (active_) {
286 IMECandidateWindowHandlerInterface* cw_handler =
287 IMEBridge::Get()->GetCandidateWindowHandler();
288 if (cw_handler)
289 cw_handler->UpdateLookupTable(*candidate_window_, window_visible_);
293 bool InputMethodEngine::SetCandidateWindowVisible(bool visible,
294 std::string* error) {
295 if (!active_) {
296 *error = kErrorNotActive;
297 return false;
300 window_visible_ = visible;
301 IMECandidateWindowHandlerInterface* cw_handler =
302 IMEBridge::Get()->GetCandidateWindowHandler();
303 if (cw_handler)
304 cw_handler->UpdateLookupTable(*candidate_window_, window_visible_);
305 return true;
308 bool InputMethodEngine::SetCandidates(
309 int context_id,
310 const std::vector<Candidate>& candidates,
311 std::string* error) {
312 if (!active_) {
313 *error = kErrorNotActive;
314 return false;
316 if (context_id != context_id_ || context_id_ == -1) {
317 *error = kErrorWrongContext;
318 return false;
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);
340 if (active_) {
341 IMECandidateWindowHandlerInterface* cw_handler =
342 IMEBridge::Get()->GetCandidateWindowHandler();
343 if (cw_handler)
344 cw_handler->UpdateLookupTable(*candidate_window_, window_visible_);
346 return true;
349 bool InputMethodEngine::SetCursorPosition(int context_id, int candidate_id,
350 std::string* error) {
351 if (!active_) {
352 *error = kErrorNotActive;
353 return false;
355 if (context_id != context_id_ || context_id_ == -1) {
356 *error = kErrorWrongContext;
357 return false;
360 std::map<int, int>::const_iterator position =
361 candidate_indexes_.find(candidate_id);
362 if (position == candidate_indexes_.end()) {
363 *error = kCandidateNotFound;
364 return false;
367 candidate_window_->set_cursor_position(position->second);
368 IMECandidateWindowHandlerInterface* cw_handler =
369 IMEBridge::Get()->GetCandidateWindowHandler();
370 if (cw_handler)
371 cw_handler->UpdateLookupTable(*candidate_window_, window_visible_);
372 return true;
375 bool InputMethodEngine::SetMenuItems(const std::vector<MenuItem>& items) {
376 return UpdateMenuItems(items);
379 bool InputMethodEngine::UpdateMenuItems(
380 const std::vector<MenuItem>& items) {
381 if (!active_)
382 return false;
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(
394 menu_item_list);
395 return true;
398 bool InputMethodEngine::IsActive() const {
399 return active_;
402 void InputMethodEngine::KeyEventDone(input_method::KeyEventHandle* key_data,
403 bool handled) {
404 KeyEventDoneCallback* callback =
405 reinterpret_cast<KeyEventDoneCallback*>(key_data);
406 callback->Run(handled);
407 delete callback;
410 bool InputMethodEngine::DeleteSurroundingText(int context_id,
411 int offset,
412 size_t number_of_chars,
413 std::string* error) {
414 if (!active_) {
415 *error = kErrorNotActive;
416 return false;
418 if (context_id != context_id_ || context_id_ == -1) {
419 *error = kErrorWrongContext;
420 return false;
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();
430 if (input_context)
431 input_context->DeleteSurroundingText(offset, number_of_chars);
433 return true;
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;
457 if (!active_)
458 return;
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);
467 return;
470 context_id_ = next_context_id_;
471 ++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";
478 break;
479 case ui::TEXT_INPUT_TYPE_TELEPHONE:
480 context.type = "tel";
481 break;
482 case ui::TEXT_INPUT_TYPE_URL:
483 context.type = "url";
484 break;
485 case ui::TEXT_INPUT_TYPE_EMAIL:
486 context.type = "email";
487 break;
488 case ui::TEXT_INPUT_TYPE_NUMBER:
489 context.type = "number";
490 break;
491 case ui::TEXT_INPUT_TYPE_PASSWORD:
492 context.type = "password";
493 break;
494 default:
495 context.type = "text";
496 break;
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;
505 if (!active_)
506 return;
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);
513 return;
516 int context_id = context_id_;
517 context_id_ = -1;
518 observer_->OnBlur(context_id);
521 void InputMethodEngine::Enable() {
522 active_ = true;
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() {
531 active_ = false;
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_);
543 namespace {
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);
549 DCHECK(ext_event);
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);
566 x11_keysym = keysym;
567 } else {
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);
579 } // namespace
581 void InputMethodEngine::ProcessKeyEvent(
582 const ui::KeyEvent& key_event,
583 const KeyEventDoneCallback& callback) {
585 KeyEventDoneCallback *handler = new KeyEventDoneCallback();
586 *handler = callback;
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(
599 engine_id_,
600 ext_event,
601 reinterpret_cast<input_method::KeyEventHandle*>(handler));
604 void InputMethodEngine::CandidateClicked(uint32 index) {
605 if (index > candidate_ids_.size()) {
606 return;
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,
615 uint32 cursor_pos,
616 uint32 anchor_pos) {
617 observer_->OnSurroundingTextChanged(engine_id_,
618 text,
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.
644 } else {
645 switch (item.style) {
646 case MENU_ITEM_STYLE_NONE:
647 NOTREACHED();
648 break;
649 case MENU_ITEM_STYLE_CHECK:
650 // TODO(nona): Implement it.
651 break;
652 case MENU_ITEM_STYLE_RADIO:
653 property->is_selection_item = true;
654 break;
655 case MENU_ITEM_STYLE_SEPARATOR:
656 // TODO(nona): Implement it.
657 break;
662 // TODO(nona): Support item.children.
665 } // namespace chromeos