Chromium Code Reviews| 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_interface.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 std::string GetKeyFromEvent(const ui::KeyEvent& event) { | |
| 75 const std::string code = event.GetCodeString(); | |
| 76 if (base::StartsWith(code, "Control", base::CompareCase::SENSITIVE)) | |
| 77 return "Ctrl"; | |
| 78 if (base::StartsWith(code, "Shift", base::CompareCase::SENSITIVE)) | |
| 79 return "Shift"; | |
| 80 if (base::StartsWith(code, "Alt", base::CompareCase::SENSITIVE)) | |
| 81 return "Alt"; | |
| 82 if (base::StartsWith(code, "Arrow", base::CompareCase::SENSITIVE)) | |
| 83 return code.substr(5); | |
| 84 if (code == "Escape") | |
| 85 return "Esc"; | |
| 86 if (code == "Backspace" || code == "Tab" || code == "Enter" || | |
| 87 code == "CapsLock" || code == "Power") | |
| 88 return code; | |
| 89 // Cases for media keys. | |
| 90 switch (event.key_code()) { | |
| 91 case ui::VKEY_BROWSER_BACK: | |
| 92 case ui::VKEY_F1: | |
| 93 return "HistoryBack"; | |
| 94 case ui::VKEY_BROWSER_FORWARD: | |
| 95 case ui::VKEY_F2: | |
| 96 return "HistoryForward"; | |
| 97 case ui::VKEY_BROWSER_REFRESH: | |
| 98 case ui::VKEY_F3: | |
| 99 return "BrowserRefresh"; | |
| 100 case ui::VKEY_MEDIA_LAUNCH_APP2: | |
| 101 case ui::VKEY_F4: | |
| 102 return "ChromeOSFullscreen"; | |
| 103 case ui::VKEY_MEDIA_LAUNCH_APP1: | |
| 104 case ui::VKEY_F5: | |
| 105 return "ChromeOSSwitchWindow"; | |
| 106 #if !defined(OS_WIN) | |
| 107 // ui::VKEY_F6 and ui::VKEY_F7 are unassigned on Windows. | |
|
Shu Chen
2016/01/06 20:36:10
I don't understand this, can you please elaborate
Azure Wei
2016/01/07 03:03:12
Sorry, just VKEY_BRIGHTNESS_DOWN/VKEY_BRIGHTNESS_U
| |
| 108 case ui::VKEY_BRIGHTNESS_DOWN: | |
| 109 case ui::VKEY_F6: | |
| 110 return "BrightnessDown"; | |
| 111 case ui::VKEY_BRIGHTNESS_UP: | |
| 112 case ui::VKEY_F7: | |
| 113 return "BrightnessUp"; | |
| 114 #endif //! defined(OS_WIN) | |
|
Shu Chen
2016/01/06 20:36:10
// !defined(OS_WIN)
Azure Wei
2016/01/07 03:03:12
Done.
| |
| 115 case ui::VKEY_VOLUME_MUTE: | |
| 116 case ui::VKEY_F8: | |
| 117 return "AudioVolumeMute"; | |
| 118 case ui::VKEY_VOLUME_DOWN: | |
| 119 case ui::VKEY_F9: | |
| 120 return "AudioVolumeDown"; | |
| 121 case ui::VKEY_VOLUME_UP: | |
| 122 case ui::VKEY_F10: | |
| 123 return "AudioVolumeUp"; | |
| 124 default: | |
| 125 break; | |
| 126 } | |
| 127 uint16_t ch = 0; | |
| 128 // Ctrl+? cases, gets key value for Ctrl is not down. | |
| 129 if (event.flags() & ui::EF_CONTROL_DOWN) { | |
| 130 ui::KeyEvent event_no_ctrl(event.type(), event.key_code(), | |
| 131 event.flags() ^ ui::EF_CONTROL_DOWN); | |
| 132 ch = event_no_ctrl.GetCharacter(); | |
| 133 } else { | |
| 134 ch = event.GetCharacter(); | |
| 135 } | |
| 136 return base::UTF16ToUTF8(base::string16(1, ch)); | |
| 137 } | |
| 138 | |
| 139 void GetExtensionKeyboardEventFromKeyEvent( | |
| 140 const ui::KeyEvent& event, | |
| 141 InputMethodEngineInterface::KeyboardEvent* ext_event) { | |
| 142 DCHECK(event.type() == ui::ET_KEY_RELEASED || | |
| 143 event.type() == ui::ET_KEY_PRESSED); | |
| 144 DCHECK(ext_event); | |
| 145 ext_event->type = (event.type() == ui::ET_KEY_RELEASED) ? "keyup" : "keydown"; | |
| 146 | |
| 147 if (event.code() == ui::DomCode::NONE) { | |
| 148 // TODO(azurewei): Use KeycodeConverter::DomCodeToCodeString on all platforms | |
| 149 #if defined(OS_CHROMEOS) | |
| 150 ext_event->code = ui::KeyboardCodeToDomKeycode(event.key_code()); | |
| 151 #else | |
| 152 ext_event->code = | |
| 153 std::string(ui::KeycodeConverter::DomCodeToCodeString(event.code())); | |
| 154 #endif | |
| 155 } else { | |
| 156 ext_event->code = event.GetCodeString(); | |
| 157 } | |
| 158 ext_event->key_code = static_cast<int>(event.key_code()); | |
| 159 ext_event->alt_key = event.IsAltDown(); | |
| 160 ext_event->ctrl_key = event.IsControlDown(); | |
| 161 ext_event->shift_key = event.IsShiftDown(); | |
| 162 ext_event->caps_lock = event.IsCapsLockDown(); | |
| 163 ext_event->key = GetKeyFromEvent(event); | |
| 164 } | |
| 165 | |
| 166 } // namespace | |
| 167 | |
| 168 InputMethodEngineInterface::InputMethodEngineInterface() | |
| 169 : current_input_type_(ui::TEXT_INPUT_TYPE_NONE), | |
| 170 context_id_(0), | |
| 171 next_context_id_(1), | |
| 172 composition_text_(new ui::CompositionText()), | |
| 173 composition_cursor_(0), | |
| 174 sent_key_event_(NULL), | |
| 175 profile_(NULL) {} | |
| 176 | |
| 177 InputMethodEngineInterface::~InputMethodEngineInterface() {} | |
| 178 | |
| 179 void InputMethodEngineInterface::Initialize( | |
| 180 scoped_ptr<ui::IMEEngineObserver> observer, | |
| 181 const char* extension_id, | |
| 182 Profile* profile) { | |
| 183 DCHECK(observer) << "Observer must not be null."; | |
| 184 | |
| 185 // TODO(komatsu): It is probably better to set observer out of Initialize. | |
| 186 observer_ = std::move(observer); | |
| 187 extension_id_ = extension_id; | |
| 188 profile_ = profile; | |
| 189 } | |
| 190 | |
| 191 const std::string& InputMethodEngineInterface::GetActiveComponentId() const { | |
| 192 return active_component_id_; | |
| 193 } | |
| 194 | |
| 195 bool InputMethodEngineInterface::SetComposition( | |
| 196 int context_id, | |
| 197 const char* text, | |
| 198 int selection_start, | |
| 199 int selection_end, | |
| 200 int cursor, | |
| 201 const std::vector<SegmentInfo>& segments, | |
| 202 std::string* error) { | |
| 203 if (!IsActive()) { | |
| 204 *error = kErrorNotActive; | |
| 205 return false; | |
| 206 } | |
| 207 if (context_id != context_id_ || context_id_ == -1) { | |
| 208 *error = kErrorWrongContext; | |
| 209 return false; | |
| 210 } | |
| 211 | |
| 212 composition_cursor_ = cursor; | |
| 213 composition_text_.reset(new ui::CompositionText()); | |
| 214 composition_text_->text = base::UTF8ToUTF16(text); | |
| 215 | |
| 216 composition_text_->selection.set_start(selection_start); | |
| 217 composition_text_->selection.set_end(selection_end); | |
| 218 | |
| 219 // TODO: Add support for displaying selected text in the composition string. | |
| 220 for (std::vector<SegmentInfo>::const_iterator segment = segments.begin(); | |
| 221 segment != segments.end(); ++segment) { | |
| 222 ui::CompositionUnderline underline; | |
| 223 | |
| 224 switch (segment->style) { | |
| 225 case SEGMENT_STYLE_UNDERLINE: | |
| 226 underline.color = SK_ColorBLACK; | |
| 227 break; | |
| 228 case SEGMENT_STYLE_DOUBLE_UNDERLINE: | |
| 229 underline.color = SK_ColorBLACK; | |
| 230 underline.thick = true; | |
| 231 break; | |
| 232 case SEGMENT_STYLE_NO_UNDERLINE: | |
| 233 underline.color = SK_ColorTRANSPARENT; | |
| 234 break; | |
| 235 default: | |
| 236 continue; | |
| 237 } | |
| 238 | |
| 239 underline.start_offset = segment->start; | |
| 240 underline.end_offset = segment->end; | |
| 241 composition_text_->underlines.push_back(underline); | |
| 242 } | |
| 243 | |
| 244 // TODO(nona): Makes focus out mode configuable, if necessary. | |
| 245 UpdateComposition(*composition_text_, composition_cursor_, true); | |
| 246 return true; | |
| 247 } | |
| 248 | |
| 249 bool InputMethodEngineInterface::ClearComposition(int context_id, | |
| 250 std::string* error) { | |
| 251 if (!IsActive()) { | |
| 252 *error = kErrorNotActive; | |
| 253 return false; | |
| 254 } | |
| 255 if (context_id != context_id_ || context_id_ == -1) { | |
| 256 *error = kErrorWrongContext; | |
| 257 return false; | |
| 258 } | |
| 259 | |
| 260 composition_cursor_ = 0; | |
| 261 composition_text_.reset(new ui::CompositionText()); | |
| 262 UpdateComposition(*composition_text_, composition_cursor_, false); | |
| 263 return true; | |
| 264 } | |
| 265 | |
| 266 bool InputMethodEngineInterface::CommitText(int context_id, | |
| 267 const char* text, | |
| 268 std::string* error) { | |
| 269 if (!IsActive()) { | |
| 270 // TODO: Commit the text anyways. | |
| 271 *error = kErrorNotActive; | |
| 272 return false; | |
| 273 } | |
| 274 if (context_id != context_id_ || context_id_ == -1) { | |
| 275 *error = kErrorWrongContext; | |
| 276 return false; | |
| 277 } | |
| 278 | |
| 279 ui::IMEBridge::Get()->GetInputContextHandler()->CommitText(text); | |
| 280 | |
| 281 // Records histograms for committed characters. | |
| 282 if (!composition_text_->text.empty()) { | |
| 283 size_t len = GetUtf8StringLength(text); | |
| 284 UMA_HISTOGRAM_CUSTOM_COUNTS("InputMethod.CommitLength", len, 1, 25, 25); | |
| 285 composition_text_.reset(new ui::CompositionText()); | |
| 286 } | |
| 287 return true; | |
| 288 } | |
| 289 | |
| 290 bool InputMethodEngineInterface::DeleteSurroundingText(int context_id, | |
| 291 int offset, | |
| 292 size_t number_of_chars, | |
| 293 std::string* error) { | |
| 294 if (!IsActive()) { | |
| 295 *error = kErrorNotActive; | |
| 296 return false; | |
| 297 } | |
| 298 if (context_id != context_id_ || context_id_ == -1) { | |
| 299 *error = kErrorWrongContext; | |
| 300 return false; | |
| 301 } | |
| 302 | |
| 303 // TODO(nona): Return false if there is ongoing composition. | |
| 304 | |
| 305 ui::IMEInputContextHandlerInterface* input_context = | |
| 306 ui::IMEBridge::Get()->GetInputContextHandler(); | |
| 307 if (input_context) | |
| 308 input_context->DeleteSurroundingText(offset, number_of_chars); | |
| 309 | |
| 310 return true; | |
| 311 } | |
| 312 | |
| 313 void InputMethodEngineInterface::SetCompositionBounds( | |
| 314 const std::vector<gfx::Rect>& bounds) { | |
| 315 if (!CheckProfile()) | |
| 316 return; | |
| 317 observer_->OnCompositionBoundsChanged(bounds); | |
| 318 } | |
| 319 | |
| 320 void InputMethodEngineInterface::FocusIn( | |
| 321 const ui::IMEEngineHandlerInterface::InputContext& input_context) { | |
| 322 if (!CheckProfile()) | |
| 323 return; | |
| 324 current_input_type_ = input_context.type; | |
| 325 | |
| 326 if (!IsActive() || current_input_type_ == ui::TEXT_INPUT_TYPE_NONE) | |
| 327 return; | |
| 328 | |
| 329 context_id_ = next_context_id_; | |
| 330 ++next_context_id_; | |
| 331 | |
| 332 observer_->OnFocus(ui::IMEEngineHandlerInterface::InputContext( | |
| 333 context_id_, input_context.type, input_context.mode, | |
| 334 input_context.flags)); | |
| 335 } | |
| 336 | |
| 337 void InputMethodEngineInterface::FocusOut() { | |
| 338 if (!CheckProfile()) | |
| 339 return; | |
| 340 if (!IsActive() || current_input_type_ == ui::TEXT_INPUT_TYPE_NONE) | |
| 341 return; | |
| 342 | |
| 343 current_input_type_ = ui::TEXT_INPUT_TYPE_NONE; | |
| 344 | |
| 345 int context_id = context_id_; | |
| 346 context_id_ = -1; | |
| 347 observer_->OnBlur(context_id); | |
| 348 } | |
| 349 | |
| 350 void InputMethodEngineInterface::Enable(const std::string& component_id) { | |
| 351 if (!CheckProfile()) | |
| 352 return; | |
| 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 InputMethodEngineInterface::Disable() { | |
| 363 if (!CheckProfile()) | |
| 364 return; | |
| 365 active_component_id_.clear(); | |
| 366 if (ui::IMEBridge::Get()->GetInputContextHandler()) | |
| 367 ui::IMEBridge::Get()->GetInputContextHandler()->CommitText( | |
| 368 base::UTF16ToUTF8(composition_text_->text)); | |
| 369 composition_text_.reset(new ui::CompositionText()); | |
| 370 observer_->OnDeactivated(active_component_id_); | |
| 371 } | |
| 372 | |
| 373 void InputMethodEngineInterface::Reset() { | |
| 374 if (!CheckProfile()) | |
| 375 return; | |
| 376 composition_text_.reset(new ui::CompositionText()); | |
| 377 observer_->OnReset(active_component_id_); | |
| 378 } | |
| 379 | |
| 380 bool InputMethodEngineInterface::IsInterestedInKeyEvent() const { | |
| 381 return observer_->IsInterestedInKeyEvent(); | |
| 382 } | |
| 383 | |
| 384 void InputMethodEngineInterface::ProcessKeyEvent( | |
| 385 const ui::KeyEvent& key_event, | |
| 386 KeyEventDoneCallback& callback) { | |
| 387 if (!CheckProfile()) | |
| 388 return; | |
| 389 | |
| 390 KeyboardEvent ext_event; | |
| 391 GetExtensionKeyboardEventFromKeyEvent(key_event, &ext_event); | |
| 392 | |
| 393 // If the given key event is equal to the key event sent by | |
| 394 // SendKeyEvents, this engine ID is propagated to the extension IME. | |
| 395 // Note, this check relies on that ui::KeyEvent is propagated as | |
| 396 // reference without copying. | |
| 397 if (&key_event == sent_key_event_) | |
| 398 ext_event.extension_id = extension_id_; | |
| 399 | |
| 400 observer_->OnKeyEvent(active_component_id_, ext_event, callback); | |
| 401 } | |
| 402 | |
| 403 void InputMethodEngineInterface::SetSurroundingText(const std::string& text, | |
| 404 uint32_t cursor_pos, | |
| 405 uint32_t anchor_pos, | |
| 406 uint32_t offset_pos) { | |
| 407 if (!CheckProfile()) | |
| 408 return; | |
| 409 observer_->OnSurroundingTextChanged( | |
| 410 active_component_id_, text, static_cast<int>(cursor_pos), | |
| 411 static_cast<int>(anchor_pos), static_cast<int>(offset_pos)); | |
| 412 } | |
| 413 | |
| 414 } // namespace input_method | |
| OLD | NEW |