| OLD | NEW |
| (Empty) |
| 1 // Copyright 2016 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. | |
| 4 | |
| 5 #include "chrome/browser/input_method/input_method_engine_base.h" | |
| 6 | |
| 7 #undef FocusIn | |
| 8 #undef FocusOut | |
| 9 #undef RootWindow | |
| 10 #include <algorithm> | |
| 11 #include <map> | |
| 12 | |
| 13 #include "base/logging.h" | |
| 14 #include "base/memory/scoped_ptr.h" | |
| 15 #include "base/metrics/histogram.h" | |
| 16 #include "base/strings/string_number_conversions.h" | |
| 17 #include "base/strings/string_util.h" | |
| 18 #include "base/strings/stringprintf.h" | |
| 19 #include "base/strings/utf_string_conversions.h" | |
| 20 #include "chrome/browser/profiles/profile_manager.h" | |
| 21 #include "ui/aura/window.h" | |
| 22 #include "ui/aura/window_tree_host.h" | |
| 23 #include "ui/base/ime/composition_text.h" | |
| 24 #include "ui/base/ime/ime_bridge.h" | |
| 25 #include "ui/base/ime/text_input_flags.h" | |
| 26 #include "ui/events/event.h" | |
| 27 #include "ui/events/event_processor.h" | |
| 28 #include "ui/events/event_utils.h" | |
| 29 #include "ui/events/keycodes/dom/dom_code.h" | |
| 30 #include "ui/keyboard/keyboard_controller.h" | |
| 31 #include "ui/keyboard/keyboard_util.h" | |
| 32 | |
| 33 #if defined(OS_CHROMEOS) | |
| 34 #include "ui/base/ime/chromeos/ime_keymap.h" | |
| 35 #elif defined(OS_WIN) | |
| 36 #include "ui/events/keycodes/dom/keycode_converter.h" | |
| 37 #include "ui/events/keycodes/keyboard_codes_win.h" | |
| 38 #elif defined(OS_LINUX) | |
| 39 #include "ui/events/keycodes/dom/keycode_converter.h" | |
| 40 #include "ui/events/keycodes/keyboard_codes_posix.h" | |
| 41 #endif | |
| 42 | |
| 43 namespace input_method { | |
| 44 | |
| 45 namespace { | |
| 46 | |
| 47 const char kErrorNotActive[] = "IME is not active"; | |
| 48 const char kErrorWrongContext[] = "Context is not active"; | |
| 49 | |
| 50 // Notifies InputContextHandler that the composition is changed. | |
| 51 void UpdateComposition(const ui::CompositionText& composition_text, | |
| 52 uint32_t cursor_pos, | |
| 53 bool is_visible) { | |
| 54 ui::IMEInputContextHandlerInterface* input_context = | |
| 55 ui::IMEBridge::Get()->GetInputContextHandler(); | |
| 56 if (input_context) | |
| 57 input_context->UpdateCompositionText(composition_text, cursor_pos, | |
| 58 is_visible); | |
| 59 } | |
| 60 | |
| 61 // Returns the length of characters of a UTF-8 string with unknown string | |
| 62 // length. Cannot apply faster algorithm to count characters in an utf-8 | |
| 63 // string without knowing the string length, so just does a full scan. | |
| 64 size_t GetUtf8StringLength(const char* s) { | |
| 65 size_t ret = 0; | |
| 66 while (*s) { | |
| 67 if ((*s & 0xC0) != 0x80) | |
| 68 ret++; | |
| 69 ++s; | |
| 70 } | |
| 71 return ret; | |
| 72 } | |
| 73 | |
| 74 #if defined(OS_CHROMEOS) | |
| 75 std::string GetKeyFromEvent(const ui::KeyEvent& event) { | |
| 76 const std::string code = event.GetCodeString(); | |
| 77 if (base::StartsWith(code, "Control", base::CompareCase::SENSITIVE)) | |
| 78 return "Ctrl"; | |
| 79 if (base::StartsWith(code, "Shift", base::CompareCase::SENSITIVE)) | |
| 80 return "Shift"; | |
| 81 if (base::StartsWith(code, "Alt", base::CompareCase::SENSITIVE)) | |
| 82 return "Alt"; | |
| 83 if (base::StartsWith(code, "Arrow", base::CompareCase::SENSITIVE)) | |
| 84 return code.substr(5); | |
| 85 if (code == "Escape") | |
| 86 return "Esc"; | |
| 87 if (code == "Backspace" || code == "Tab" || code == "Enter" || | |
| 88 code == "CapsLock" || code == "Power") | |
| 89 return code; | |
| 90 // Cases for media keys. | |
| 91 switch (event.key_code()) { | |
| 92 case ui::VKEY_BROWSER_BACK: | |
| 93 case ui::VKEY_F1: | |
| 94 return "HistoryBack"; | |
| 95 case ui::VKEY_BROWSER_FORWARD: | |
| 96 case ui::VKEY_F2: | |
| 97 return "HistoryForward"; | |
| 98 case ui::VKEY_BROWSER_REFRESH: | |
| 99 case ui::VKEY_F3: | |
| 100 return "BrowserRefresh"; | |
| 101 case ui::VKEY_MEDIA_LAUNCH_APP2: | |
| 102 case ui::VKEY_F4: | |
| 103 return "ChromeOSFullscreen"; | |
| 104 case ui::VKEY_MEDIA_LAUNCH_APP1: | |
| 105 case ui::VKEY_F5: | |
| 106 return "ChromeOSSwitchWindow"; | |
| 107 case ui::VKEY_BRIGHTNESS_DOWN: | |
| 108 case ui::VKEY_F6: | |
| 109 return "BrightnessDown"; | |
| 110 case ui::VKEY_BRIGHTNESS_UP: | |
| 111 case ui::VKEY_F7: | |
| 112 return "BrightnessUp"; | |
| 113 case ui::VKEY_VOLUME_MUTE: | |
| 114 case ui::VKEY_F8: | |
| 115 return "AudioVolumeMute"; | |
| 116 case ui::VKEY_VOLUME_DOWN: | |
| 117 case ui::VKEY_F9: | |
| 118 return "AudioVolumeDown"; | |
| 119 case ui::VKEY_VOLUME_UP: | |
| 120 case ui::VKEY_F10: | |
| 121 return "AudioVolumeUp"; | |
| 122 default: | |
| 123 break; | |
| 124 } | |
| 125 uint16_t ch = 0; | |
| 126 // Ctrl+? cases, gets key value for Ctrl is not down. | |
| 127 if (event.flags() & ui::EF_CONTROL_DOWN) { | |
| 128 ui::KeyEvent event_no_ctrl(event.type(), event.key_code(), | |
| 129 event.flags() ^ ui::EF_CONTROL_DOWN); | |
| 130 ch = event_no_ctrl.GetCharacter(); | |
| 131 } else { | |
| 132 ch = event.GetCharacter(); | |
| 133 } | |
| 134 return base::UTF16ToUTF8(base::string16(1, ch)); | |
| 135 } | |
| 136 #endif // defined(OS_CHROMEOS) | |
| 137 | |
| 138 void GetExtensionKeyboardEventFromKeyEvent( | |
| 139 const ui::KeyEvent& event, | |
| 140 InputMethodEngineBase::KeyboardEvent* ext_event) { | |
| 141 DCHECK(event.type() == ui::ET_KEY_RELEASED || | |
| 142 event.type() == ui::ET_KEY_PRESSED); | |
| 143 DCHECK(ext_event); | |
| 144 ext_event->type = (event.type() == ui::ET_KEY_RELEASED) ? "keyup" : "keydown"; | |
| 145 | |
| 146 if (event.code() == ui::DomCode::NONE) { | |
| 147 // TODO(azurewei): Use KeycodeConverter::DomCodeToCodeString on all platforms | |
| 148 #if defined(OS_CHROMEOS) | |
| 149 ext_event->code = ui::KeyboardCodeToDomKeycode(event.key_code()); | |
| 150 #else | |
| 151 ext_event->code = | |
| 152 std::string(ui::KeycodeConverter::DomCodeToCodeString(event.code())); | |
| 153 #endif | |
| 154 } else { | |
| 155 ext_event->code = event.GetCodeString(); | |
| 156 } | |
| 157 ext_event->key_code = static_cast<int>(event.key_code()); | |
| 158 ext_event->alt_key = event.IsAltDown(); | |
| 159 ext_event->ctrl_key = event.IsControlDown(); | |
| 160 ext_event->shift_key = event.IsShiftDown(); | |
| 161 ext_event->caps_lock = event.IsCapsLockOn(); | |
| 162 #if defined(OS_CHROMEOS) | |
| 163 ext_event->key = GetKeyFromEvent(event); | |
| 164 #else | |
| 165 ext_event->key = ui::KeycodeConverter::DomKeyToKeyString(event.GetDomKey()); | |
| 166 #endif // defined(OS_CHROMEOS) | |
| 167 } | |
| 168 | |
| 169 } // namespace | |
| 170 | |
| 171 InputMethodEngineBase::KeyboardEvent::KeyboardEvent() | |
| 172 : alt_key(false), ctrl_key(false), shift_key(false), caps_lock(false) {} | |
| 173 | |
| 174 InputMethodEngineBase::KeyboardEvent::~KeyboardEvent() {} | |
| 175 | |
| 176 InputMethodEngineBase::InputMethodEngineBase() | |
| 177 : current_input_type_(ui::TEXT_INPUT_TYPE_NONE), | |
| 178 context_id_(0), | |
| 179 next_context_id_(1), | |
| 180 composition_text_(new ui::CompositionText()), | |
| 181 composition_cursor_(0), | |
| 182 sent_key_event_(NULL), | |
| 183 profile_(NULL) {} | |
| 184 | |
| 185 InputMethodEngineBase::~InputMethodEngineBase() {} | |
| 186 | |
| 187 void InputMethodEngineBase::Initialize( | |
| 188 scoped_ptr<InputMethodEngineBase::Observer> observer, | |
| 189 const char* extension_id, | |
| 190 Profile* profile) { | |
| 191 DCHECK(observer) << "Observer must not be null."; | |
| 192 | |
| 193 // TODO(komatsu): It is probably better to set observer out of Initialize. | |
| 194 observer_ = std::move(observer); | |
| 195 extension_id_ = extension_id; | |
| 196 profile_ = profile; | |
| 197 } | |
| 198 | |
| 199 const std::string& InputMethodEngineBase::GetActiveComponentId() const { | |
| 200 return active_component_id_; | |
| 201 } | |
| 202 | |
| 203 bool InputMethodEngineBase::SetComposition( | |
| 204 int context_id, | |
| 205 const char* text, | |
| 206 int selection_start, | |
| 207 int selection_end, | |
| 208 int cursor, | |
| 209 const std::vector<SegmentInfo>& segments, | |
| 210 std::string* error) { | |
| 211 if (!IsActive()) { | |
| 212 *error = kErrorNotActive; | |
| 213 return false; | |
| 214 } | |
| 215 if (context_id != context_id_ || context_id_ == -1) { | |
| 216 *error = kErrorWrongContext; | |
| 217 return false; | |
| 218 } | |
| 219 | |
| 220 composition_cursor_ = cursor; | |
| 221 composition_text_.reset(new ui::CompositionText()); | |
| 222 composition_text_->text = base::UTF8ToUTF16(text); | |
| 223 | |
| 224 composition_text_->selection.set_start(selection_start); | |
| 225 composition_text_->selection.set_end(selection_end); | |
| 226 | |
| 227 // TODO: Add support for displaying selected text in the composition string. | |
| 228 for (std::vector<SegmentInfo>::const_iterator segment = segments.begin(); | |
| 229 segment != segments.end(); ++segment) { | |
| 230 ui::CompositionUnderline underline; | |
| 231 | |
| 232 switch (segment->style) { | |
| 233 case SEGMENT_STYLE_UNDERLINE: | |
| 234 underline.color = SK_ColorBLACK; | |
| 235 break; | |
| 236 case SEGMENT_STYLE_DOUBLE_UNDERLINE: | |
| 237 underline.color = SK_ColorBLACK; | |
| 238 underline.thick = true; | |
| 239 break; | |
| 240 case SEGMENT_STYLE_NO_UNDERLINE: | |
| 241 underline.color = SK_ColorTRANSPARENT; | |
| 242 break; | |
| 243 default: | |
| 244 continue; | |
| 245 } | |
| 246 | |
| 247 underline.start_offset = segment->start; | |
| 248 underline.end_offset = segment->end; | |
| 249 composition_text_->underlines.push_back(underline); | |
| 250 } | |
| 251 | |
| 252 // TODO(nona): Makes focus out mode configuable, if necessary. | |
| 253 UpdateComposition(*composition_text_, composition_cursor_, true); | |
| 254 return true; | |
| 255 } | |
| 256 | |
| 257 bool InputMethodEngineBase::ClearComposition(int context_id, | |
| 258 std::string* error) { | |
| 259 if (!IsActive()) { | |
| 260 *error = kErrorNotActive; | |
| 261 return false; | |
| 262 } | |
| 263 if (context_id != context_id_ || context_id_ == -1) { | |
| 264 *error = kErrorWrongContext; | |
| 265 return false; | |
| 266 } | |
| 267 | |
| 268 composition_cursor_ = 0; | |
| 269 composition_text_.reset(new ui::CompositionText()); | |
| 270 UpdateComposition(*composition_text_, composition_cursor_, false); | |
| 271 return true; | |
| 272 } | |
| 273 | |
| 274 bool InputMethodEngineBase::CommitText(int context_id, | |
| 275 const char* text, | |
| 276 std::string* error) { | |
| 277 if (!IsActive()) { | |
| 278 // TODO: Commit the text anyways. | |
| 279 *error = kErrorNotActive; | |
| 280 return false; | |
| 281 } | |
| 282 if (context_id != context_id_ || context_id_ == -1) { | |
| 283 *error = kErrorWrongContext; | |
| 284 return false; | |
| 285 } | |
| 286 | |
| 287 ui::IMEBridge::Get()->GetInputContextHandler()->CommitText(text); | |
| 288 | |
| 289 // Records histograms for committed characters. | |
| 290 if (!composition_text_->text.empty()) { | |
| 291 size_t len = GetUtf8StringLength(text); | |
| 292 UMA_HISTOGRAM_CUSTOM_COUNTS("InputMethod.CommitLength", len, 1, 25, 25); | |
| 293 composition_text_.reset(new ui::CompositionText()); | |
| 294 } | |
| 295 return true; | |
| 296 } | |
| 297 | |
| 298 bool InputMethodEngineBase::DeleteSurroundingText(int context_id, | |
| 299 int offset, | |
| 300 size_t number_of_chars, | |
| 301 std::string* error) { | |
| 302 if (!IsActive()) { | |
| 303 *error = kErrorNotActive; | |
| 304 return false; | |
| 305 } | |
| 306 if (context_id != context_id_ || context_id_ == -1) { | |
| 307 *error = kErrorWrongContext; | |
| 308 return false; | |
| 309 } | |
| 310 | |
| 311 // TODO(nona): Return false if there is ongoing composition. | |
| 312 | |
| 313 ui::IMEInputContextHandlerInterface* input_context = | |
| 314 ui::IMEBridge::Get()->GetInputContextHandler(); | |
| 315 if (input_context) | |
| 316 input_context->DeleteSurroundingText(offset, number_of_chars); | |
| 317 | |
| 318 return true; | |
| 319 } | |
| 320 | |
| 321 void InputMethodEngineBase::SetCompositionBounds( | |
| 322 const std::vector<gfx::Rect>& bounds) { | |
| 323 observer_->OnCompositionBoundsChanged(bounds); | |
| 324 } | |
| 325 | |
| 326 void InputMethodEngineBase::FocusIn( | |
| 327 const ui::IMEEngineHandlerInterface::InputContext& input_context) { | |
| 328 current_input_type_ = input_context.type; | |
| 329 | |
| 330 if (!IsActive() || current_input_type_ == ui::TEXT_INPUT_TYPE_NONE) | |
| 331 return; | |
| 332 | |
| 333 context_id_ = next_context_id_; | |
| 334 ++next_context_id_; | |
| 335 | |
| 336 observer_->OnFocus(ui::IMEEngineHandlerInterface::InputContext( | |
| 337 context_id_, input_context.type, input_context.mode, | |
| 338 input_context.flags)); | |
| 339 } | |
| 340 | |
| 341 void InputMethodEngineBase::FocusOut() { | |
| 342 if (!IsActive() || current_input_type_ == ui::TEXT_INPUT_TYPE_NONE) | |
| 343 return; | |
| 344 | |
| 345 current_input_type_ = ui::TEXT_INPUT_TYPE_NONE; | |
| 346 | |
| 347 int context_id = context_id_; | |
| 348 context_id_ = -1; | |
| 349 observer_->OnBlur(context_id); | |
| 350 } | |
| 351 | |
| 352 void InputMethodEngineBase::Enable(const std::string& component_id) { | |
| 353 DCHECK(!component_id.empty()); | |
| 354 active_component_id_ = component_id; | |
| 355 observer_->OnActivate(component_id); | |
| 356 const ui::IMEEngineHandlerInterface::InputContext& input_context = | |
| 357 ui::IMEBridge::Get()->GetCurrentInputContext(); | |
| 358 current_input_type_ = input_context.type; | |
| 359 FocusIn(input_context); | |
| 360 } | |
| 361 | |
| 362 void InputMethodEngineBase::Disable() { | |
| 363 active_component_id_.clear(); | |
| 364 if (ui::IMEBridge::Get()->GetInputContextHandler()) | |
| 365 ui::IMEBridge::Get()->GetInputContextHandler()->CommitText( | |
| 366 base::UTF16ToUTF8(composition_text_->text)); | |
| 367 composition_text_.reset(new ui::CompositionText()); | |
| 368 observer_->OnDeactivated(active_component_id_); | |
| 369 } | |
| 370 | |
| 371 void InputMethodEngineBase::Reset() { | |
| 372 composition_text_.reset(new ui::CompositionText()); | |
| 373 observer_->OnReset(active_component_id_); | |
| 374 } | |
| 375 | |
| 376 bool InputMethodEngineBase::IsInterestedInKeyEvent() const { | |
| 377 return observer_->IsInterestedInKeyEvent(); | |
| 378 } | |
| 379 | |
| 380 void InputMethodEngineBase::ProcessKeyEvent(const ui::KeyEvent& key_event, | |
| 381 KeyEventDoneCallback& callback) { | |
| 382 KeyboardEvent ext_event; | |
| 383 GetExtensionKeyboardEventFromKeyEvent(key_event, &ext_event); | |
| 384 | |
| 385 // If the given key event is equal to the key event sent by | |
| 386 // SendKeyEvents, this engine ID is propagated to the extension IME. | |
| 387 // Note, this check relies on that ui::KeyEvent is propagated as | |
| 388 // reference without copying. | |
| 389 if (&key_event == sent_key_event_) | |
| 390 ext_event.extension_id = extension_id_; | |
| 391 | |
| 392 observer_->OnKeyEvent(active_component_id_, ext_event, callback); | |
| 393 } | |
| 394 | |
| 395 void InputMethodEngineBase::SetSurroundingText(const std::string& text, | |
| 396 uint32_t cursor_pos, | |
| 397 uint32_t anchor_pos, | |
| 398 uint32_t offset_pos) { | |
| 399 observer_->OnSurroundingTextChanged( | |
| 400 active_component_id_, text, static_cast<int>(cursor_pos), | |
| 401 static_cast<int>(anchor_pos), static_cast<int>(offset_pos)); | |
| 402 } | |
| 403 | |
| 404 } // namespace input_method | |
| OLD | NEW |