| 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::InputMethodEngineBase() |
| 172 : current_input_type_(ui::TEXT_INPUT_TYPE_NONE), |
| 173 context_id_(0), |
| 174 next_context_id_(1), |
| 175 composition_text_(new ui::CompositionText()), |
| 176 composition_cursor_(0), |
| 177 sent_key_event_(NULL), |
| 178 profile_(NULL) {} |
| 179 |
| 180 InputMethodEngineBase::~InputMethodEngineBase() {} |
| 181 |
| 182 void InputMethodEngineBase::Initialize( |
| 183 scoped_ptr<ui::IMEEngineObserver> observer, |
| 184 const char* extension_id, |
| 185 Profile* profile) { |
| 186 DCHECK(observer) << "Observer must not be null."; |
| 187 |
| 188 // TODO(komatsu): It is probably better to set observer out of Initialize. |
| 189 observer_ = std::move(observer); |
| 190 extension_id_ = extension_id; |
| 191 profile_ = profile; |
| 192 } |
| 193 |
| 194 const std::string& InputMethodEngineBase::GetActiveComponentId() const { |
| 195 return active_component_id_; |
| 196 } |
| 197 |
| 198 bool InputMethodEngineBase::SetComposition( |
| 199 int context_id, |
| 200 const char* text, |
| 201 int selection_start, |
| 202 int selection_end, |
| 203 int cursor, |
| 204 const std::vector<SegmentInfo>& segments, |
| 205 std::string* error) { |
| 206 if (!IsActive()) { |
| 207 *error = kErrorNotActive; |
| 208 return false; |
| 209 } |
| 210 if (context_id != context_id_ || context_id_ == -1) { |
| 211 *error = kErrorWrongContext; |
| 212 return false; |
| 213 } |
| 214 |
| 215 composition_cursor_ = cursor; |
| 216 composition_text_.reset(new ui::CompositionText()); |
| 217 composition_text_->text = base::UTF8ToUTF16(text); |
| 218 |
| 219 composition_text_->selection.set_start(selection_start); |
| 220 composition_text_->selection.set_end(selection_end); |
| 221 |
| 222 // TODO: Add support for displaying selected text in the composition string. |
| 223 for (std::vector<SegmentInfo>::const_iterator segment = segments.begin(); |
| 224 segment != segments.end(); ++segment) { |
| 225 ui::CompositionUnderline underline; |
| 226 |
| 227 switch (segment->style) { |
| 228 case SEGMENT_STYLE_UNDERLINE: |
| 229 underline.color = SK_ColorBLACK; |
| 230 break; |
| 231 case SEGMENT_STYLE_DOUBLE_UNDERLINE: |
| 232 underline.color = SK_ColorBLACK; |
| 233 underline.thick = true; |
| 234 break; |
| 235 case SEGMENT_STYLE_NO_UNDERLINE: |
| 236 underline.color = SK_ColorTRANSPARENT; |
| 237 break; |
| 238 default: |
| 239 continue; |
| 240 } |
| 241 |
| 242 underline.start_offset = segment->start; |
| 243 underline.end_offset = segment->end; |
| 244 composition_text_->underlines.push_back(underline); |
| 245 } |
| 246 |
| 247 // TODO(nona): Makes focus out mode configuable, if necessary. |
| 248 UpdateComposition(*composition_text_, composition_cursor_, true); |
| 249 return true; |
| 250 } |
| 251 |
| 252 bool InputMethodEngineBase::ClearComposition(int context_id, |
| 253 std::string* error) { |
| 254 if (!IsActive()) { |
| 255 *error = kErrorNotActive; |
| 256 return false; |
| 257 } |
| 258 if (context_id != context_id_ || context_id_ == -1) { |
| 259 *error = kErrorWrongContext; |
| 260 return false; |
| 261 } |
| 262 |
| 263 composition_cursor_ = 0; |
| 264 composition_text_.reset(new ui::CompositionText()); |
| 265 UpdateComposition(*composition_text_, composition_cursor_, false); |
| 266 return true; |
| 267 } |
| 268 |
| 269 bool InputMethodEngineBase::CommitText(int context_id, |
| 270 const char* text, |
| 271 std::string* error) { |
| 272 if (!IsActive()) { |
| 273 // TODO: Commit the text anyways. |
| 274 *error = kErrorNotActive; |
| 275 return false; |
| 276 } |
| 277 if (context_id != context_id_ || context_id_ == -1) { |
| 278 *error = kErrorWrongContext; |
| 279 return false; |
| 280 } |
| 281 |
| 282 ui::IMEBridge::Get()->GetInputContextHandler()->CommitText(text); |
| 283 |
| 284 // Records histograms for committed characters. |
| 285 if (!composition_text_->text.empty()) { |
| 286 size_t len = GetUtf8StringLength(text); |
| 287 UMA_HISTOGRAM_CUSTOM_COUNTS("InputMethod.CommitLength", len, 1, 25, 25); |
| 288 composition_text_.reset(new ui::CompositionText()); |
| 289 } |
| 290 return true; |
| 291 } |
| 292 |
| 293 bool InputMethodEngineBase::DeleteSurroundingText(int context_id, |
| 294 int offset, |
| 295 size_t number_of_chars, |
| 296 std::string* error) { |
| 297 if (!IsActive()) { |
| 298 *error = kErrorNotActive; |
| 299 return false; |
| 300 } |
| 301 if (context_id != context_id_ || context_id_ == -1) { |
| 302 *error = kErrorWrongContext; |
| 303 return false; |
| 304 } |
| 305 |
| 306 // TODO(nona): Return false if there is ongoing composition. |
| 307 |
| 308 ui::IMEInputContextHandlerInterface* input_context = |
| 309 ui::IMEBridge::Get()->GetInputContextHandler(); |
| 310 if (input_context) |
| 311 input_context->DeleteSurroundingText(offset, number_of_chars); |
| 312 |
| 313 return true; |
| 314 } |
| 315 |
| 316 void InputMethodEngineBase::SetCompositionBounds( |
| 317 const std::vector<gfx::Rect>& bounds) { |
| 318 observer_->OnCompositionBoundsChanged(bounds); |
| 319 } |
| 320 |
| 321 void InputMethodEngineBase::FocusIn( |
| 322 const ui::IMEEngineHandlerInterface::InputContext& input_context) { |
| 323 current_input_type_ = input_context.type; |
| 324 |
| 325 if (!IsActive() || current_input_type_ == ui::TEXT_INPUT_TYPE_NONE) |
| 326 return; |
| 327 |
| 328 context_id_ = next_context_id_; |
| 329 ++next_context_id_; |
| 330 |
| 331 observer_->OnFocus(ui::IMEEngineHandlerInterface::InputContext( |
| 332 context_id_, input_context.type, input_context.mode, |
| 333 input_context.flags)); |
| 334 } |
| 335 |
| 336 void InputMethodEngineBase::FocusOut() { |
| 337 if (!IsActive() || current_input_type_ == ui::TEXT_INPUT_TYPE_NONE) |
| 338 return; |
| 339 |
| 340 current_input_type_ = ui::TEXT_INPUT_TYPE_NONE; |
| 341 |
| 342 int context_id = context_id_; |
| 343 context_id_ = -1; |
| 344 observer_->OnBlur(context_id); |
| 345 } |
| 346 |
| 347 void InputMethodEngineBase::Enable(const std::string& component_id) { |
| 348 DCHECK(!component_id.empty()); |
| 349 active_component_id_ = component_id; |
| 350 observer_->OnActivate(component_id); |
| 351 const ui::IMEEngineHandlerInterface::InputContext& input_context = |
| 352 ui::IMEBridge::Get()->GetCurrentInputContext(); |
| 353 current_input_type_ = input_context.type; |
| 354 FocusIn(input_context); |
| 355 } |
| 356 |
| 357 void InputMethodEngineBase::Disable() { |
| 358 active_component_id_.clear(); |
| 359 if (ui::IMEBridge::Get()->GetInputContextHandler()) |
| 360 ui::IMEBridge::Get()->GetInputContextHandler()->CommitText( |
| 361 base::UTF16ToUTF8(composition_text_->text)); |
| 362 composition_text_.reset(new ui::CompositionText()); |
| 363 observer_->OnDeactivated(active_component_id_); |
| 364 } |
| 365 |
| 366 void InputMethodEngineBase::Reset() { |
| 367 composition_text_.reset(new ui::CompositionText()); |
| 368 observer_->OnReset(active_component_id_); |
| 369 } |
| 370 |
| 371 bool InputMethodEngineBase::IsInterestedInKeyEvent() const { |
| 372 return observer_->IsInterestedInKeyEvent(); |
| 373 } |
| 374 |
| 375 void InputMethodEngineBase::ProcessKeyEvent(const ui::KeyEvent& key_event, |
| 376 KeyEventDoneCallback& callback) { |
| 377 KeyboardEvent ext_event; |
| 378 GetExtensionKeyboardEventFromKeyEvent(key_event, &ext_event); |
| 379 |
| 380 // If the given key event is equal to the key event sent by |
| 381 // SendKeyEvents, this engine ID is propagated to the extension IME. |
| 382 // Note, this check relies on that ui::KeyEvent is propagated as |
| 383 // reference without copying. |
| 384 if (&key_event == sent_key_event_) |
| 385 ext_event.extension_id = extension_id_; |
| 386 |
| 387 observer_->OnKeyEvent(active_component_id_, ext_event, callback); |
| 388 } |
| 389 |
| 390 void InputMethodEngineBase::SetSurroundingText(const std::string& text, |
| 391 uint32_t cursor_pos, |
| 392 uint32_t anchor_pos, |
| 393 uint32_t offset_pos) { |
| 394 observer_->OnSurroundingTextChanged( |
| 395 active_component_id_, text, static_cast<int>(cursor_pos), |
| 396 static_cast<int>(anchor_pos), static_cast<int>(offset_pos)); |
| 397 } |
| 398 |
| 399 } // namespace input_method |
| OLD | NEW |