1 // Copyright (c) 2012 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 "ui/base/ime/input_method_win.h" 6 7 #include "base/basictypes.h" 8 #include "ui/base/ime/text_input_client.h" 9 #include "ui/base/ime/win/tsf_input_scope.h" 10 #include "ui/events/event.h" 11 #include "ui/events/event_constants.h" 12 #include "ui/events/event_utils.h" 13 #include "ui/events/keycodes/keyboard_codes.h" 14 #include "ui/gfx/win/dpi.h" 15 #include "ui/gfx/win/hwnd_util.h" 16 17 namespace ui { 18 namespace { 19 20 // Extra number of chars before and after selection (or composition) range which 21 // is returned to IME for improving conversion accuracy. 22 static const size_t kExtraNumberOfChars = 20; 23 24 } // namespace 25 26 InputMethodWin::InputMethodWin(internal::InputMethodDelegate* delegate, 27 HWND toplevel_window_handle) 28 : toplevel_window_handle_(toplevel_window_handle), 29 pending_requested_direction_(base::i18n::UNKNOWN_DIRECTION), 30 accept_carriage_return_(false), 31 active_(false), 32 enabled_(false), 33 is_candidate_popup_open_(false), 34 composing_window_handle_(NULL) { 35 SetDelegate(delegate); 36 // In non-Aura environment, appropriate callbacks to OnFocus() and OnBlur() 37 // are not implemented yet. To work around this limitation, here we use 38 // "always focused" model. 39 // TODO(ime): Fix the caller of OnFocus() and OnBlur() so that appropriate 40 // focus event will be passed. 41 InputMethodBase::OnFocus(); 42 } 43 44 void InputMethodWin::Init(bool focused) { 45 // Gets the initial input locale. 46 OnInputLocaleChanged(); 47 48 InputMethodBase::Init(focused); 49 } 50 51 void InputMethodWin::OnFocus() { 52 // Ignore OnFocus event for "always focused" model. See the comment in the 53 // constructor. 54 // TODO(ime): Implement OnFocus once the callers are fixed. 55 } 56 57 void InputMethodWin::OnBlur() { 58 // Ignore OnBlur event for "always focused" model. See the comment in the 59 // constructor. 60 // TODO(ime): Implement OnFocus once the callers are fixed. 61 } 62 63 bool InputMethodWin::OnUntranslatedIMEMessage( 64 const base::NativeEvent& event, 65 InputMethod::NativeEventResult* result) { 66 LRESULT original_result = 0; 67 BOOL handled = FALSE; 68 switch (event.message) { 69 case WM_IME_SETCONTEXT: 70 original_result = OnImeSetContext( 71 event.hwnd, event.message, event.wParam, event.lParam, &handled); 72 break; 73 case WM_IME_STARTCOMPOSITION: 74 original_result = OnImeStartComposition( 75 event.hwnd, event.message, event.wParam, event.lParam, &handled); 76 break; 77 case WM_IME_COMPOSITION: 78 original_result = OnImeComposition( 79 event.hwnd, event.message, event.wParam, event.lParam, &handled); 80 break; 81 case WM_IME_ENDCOMPOSITION: 82 original_result = OnImeEndComposition( 83 event.hwnd, event.message, event.wParam, event.lParam, &handled); 84 break; 85 case WM_IME_REQUEST: 86 original_result = OnImeRequest( 87 event.message, event.wParam, event.lParam, &handled); 88 break; 89 case WM_CHAR: 90 case WM_SYSCHAR: 91 original_result = OnChar( 92 event.hwnd, event.message, event.wParam, event.lParam, &handled); 93 break; 94 case WM_IME_NOTIFY: 95 original_result = OnImeNotify( 96 event.message, event.wParam, event.lParam, &handled); 97 break; 98 default: 99 NOTREACHED() << "Unknown IME message:" << event.message; 100 break; 101 } 102 if (result) 103 *result = original_result; 104 return !!handled; 105 } 106 107 bool InputMethodWin::DispatchKeyEvent(const ui::KeyEvent& event) { 108 if (!event.HasNativeEvent()) 109 return DispatchFabricatedKeyEvent(event); 110 111 const base::NativeEvent& native_key_event = event.native_event(); 112 if (native_key_event.message == WM_CHAR) { 113 BOOL handled; 114 OnChar(native_key_event.hwnd, native_key_event.message, 115 native_key_event.wParam, native_key_event.lParam, &handled); 116 return !!handled; // Don't send WM_CHAR for post event processing. 117 } 118 // Handles ctrl-shift key to change text direction and layout alignment. 119 if (ui::IMM32Manager::IsRTLKeyboardLayoutInstalled() && 120 !IsTextInputTypeNone()) { 121 // TODO: shouldn't need to generate a KeyEvent here. 122 const ui::KeyEvent key(native_key_event); 123 ui::KeyboardCode code = key.key_code(); 124 if (key.type() == ui::ET_KEY_PRESSED) { 125 if (code == ui::VKEY_SHIFT) { 126 base::i18n::TextDirection dir; 127 if (ui::IMM32Manager::IsCtrlShiftPressed(&dir)) 128 pending_requested_direction_ = dir; 129 } else if (code != ui::VKEY_CONTROL) { 130 pending_requested_direction_ = base::i18n::UNKNOWN_DIRECTION; 131 } 132 } else if (key.type() == ui::ET_KEY_RELEASED && 133 (code == ui::VKEY_SHIFT || code == ui::VKEY_CONTROL) && 134 pending_requested_direction_ != base::i18n::UNKNOWN_DIRECTION) { 135 GetTextInputClient()->ChangeTextDirectionAndLayoutAlignment( 136 pending_requested_direction_); 137 pending_requested_direction_ = base::i18n::UNKNOWN_DIRECTION; 138 } 139 } 140 141 return DispatchKeyEventPostIME(event); 142 } 143 144 void InputMethodWin::OnTextInputTypeChanged(const TextInputClient* client) { 145 if (!IsTextInputClientFocused(client) || !IsWindowFocused(client)) 146 return; 147 imm32_manager_.CancelIME(GetAttachedWindowHandle(client)); 148 UpdateIMEState(); 149 } 150 151 void InputMethodWin::OnCaretBoundsChanged(const TextInputClient* client) { 152 if (!enabled_ || !IsTextInputClientFocused(client) || 153 !IsWindowFocused(client)) { 154 return; 155 } 156 // The current text input type should not be NONE if |client| is focused. 157 DCHECK(!IsTextInputTypeNone()); 158 // Tentatively assume that the returned value is DIP (Density Independent 159 // Pixel). See the comment in text_input_client.h and http://crbug.com/360334. 160 const gfx::Rect dip_screen_bounds(GetTextInputClient()->GetCaretBounds()); 161 const gfx::Rect screen_bounds = gfx::win::DIPToScreenRect(dip_screen_bounds); 162 163 HWND attached_window = GetAttachedWindowHandle(client); 164 // TODO(ime): see comment in TextInputClient::GetCaretBounds(), this 165 // conversion shouldn't be necessary. 166 RECT r = {}; 167 GetClientRect(attached_window, &r); 168 POINT window_point = { screen_bounds.x(), screen_bounds.y() }; 169 ScreenToClient(attached_window, &window_point); 170 gfx::Rect caret_rect(gfx::Point(window_point.x, window_point.y), 171 screen_bounds.size()); 172 imm32_manager_.UpdateCaretRect(attached_window, caret_rect); 173 } 174 175 void InputMethodWin::CancelComposition(const TextInputClient* client) { 176 if (enabled_ && IsTextInputClientFocused(client)) 177 imm32_manager_.CancelIME(GetAttachedWindowHandle(client)); 178 } 179 180 void InputMethodWin::OnInputLocaleChanged() { 181 active_ = imm32_manager_.SetInputLanguage(); 182 locale_ = imm32_manager_.GetInputLanguageName(); 183 OnInputMethodChanged(); 184 } 185 186 std::string InputMethodWin::GetInputLocale() { 187 return locale_; 188 } 189 190 bool InputMethodWin::IsActive() { 191 return active_; 192 } 193 194 bool InputMethodWin::IsCandidatePopupOpen() const { 195 return is_candidate_popup_open_; 196 } 197 198 void InputMethodWin::OnWillChangeFocusedClient(TextInputClient* focused_before, 199 TextInputClient* focused) { 200 if (IsWindowFocused(focused_before)) 201 ConfirmCompositionText(); 202 } 203 204 void InputMethodWin::OnDidChangeFocusedClient( 205 TextInputClient* focused_before, 206 TextInputClient* focused) { 207 if (IsWindowFocused(focused)) { 208 // Force to update the input type since client's TextInputStateChanged() 209 // function might not be called if text input types before the client loses 210 // focus and after it acquires focus again are the same. 211 OnTextInputTypeChanged(focused); 212 213 UpdateIMEState(); 214 215 // Force to update caret bounds, in case the client thinks that the caret 216 // bounds has not changed. 217 OnCaretBoundsChanged(focused); 218 } 219 if (focused_before != focused) 220 accept_carriage_return_ = false; 221 } 222 223 LRESULT InputMethodWin::OnChar(HWND window_handle, 224 UINT message, 225 WPARAM wparam, 226 LPARAM lparam, 227 BOOL* handled) { 228 *handled = TRUE; 229 230 // We need to send character events to the focused text input client event if 231 // its text input type is ui::TEXT_INPUT_TYPE_NONE. 232 if (GetTextInputClient()) { 233 const base::char16 kCarriageReturn = L'\r'; 234 const base::char16 ch = static_cast<base::char16>(wparam); 235 // A mask to determine the previous key state from |lparam|. The value is 1 236 // if the key is down before the message is sent, or it is 0 if the key is 237 // up. 238 const uint32 kPrevKeyDownBit = 0x40000000; 239 if (ch == kCarriageReturn && !(lparam & kPrevKeyDownBit)) 240 accept_carriage_return_ = true; 241 // Conditionally ignore '\r' events to work around crbug.com/319100. 242 // TODO(yukawa, IME): Figure out long-term solution. 243 if (ch != kCarriageReturn || accept_carriage_return_) 244 GetTextInputClient()->InsertChar(ch, ui::GetModifiersFromKeyState()); 245 } 246 247 // Explicitly show the system menu at a good location on [Alt]+[Space]. 248 // Note: Setting |handled| to FALSE for DefWindowProc triggering of the system 249 // menu causes undesirable titlebar artifacts in the classic theme. 250 if (message == WM_SYSCHAR && wparam == VK_SPACE) 251 gfx::ShowSystemMenu(window_handle); 252 253 return 0; 254 } 255 256 LRESULT InputMethodWin::OnImeSetContext(HWND window_handle, 257 UINT message, 258 WPARAM wparam, 259 LPARAM lparam, 260 BOOL* handled) { 261 if (!!wparam) 262 imm32_manager_.CreateImeWindow(window_handle); 263 264 OnInputMethodChanged(); 265 return imm32_manager_.SetImeWindowStyle( 266 window_handle, message, wparam, lparam, handled); 267 } 268 269 LRESULT InputMethodWin::OnImeStartComposition(HWND window_handle, 270 UINT message, 271 WPARAM wparam, 272 LPARAM lparam, 273 BOOL* handled) { 274 // We have to prevent WTL from calling ::DefWindowProc() because the function 275 // calls ::ImmSetCompositionWindow() and ::ImmSetCandidateWindow() to 276 // over-write the position of IME windows. 277 *handled = TRUE; 278 279 // Reset the composition status and create IME windows. 280 composing_window_handle_ = window_handle; 281 imm32_manager_.CreateImeWindow(window_handle); 282 imm32_manager_.ResetComposition(window_handle); 283 return 0; 284 } 285 286 LRESULT InputMethodWin::OnImeComposition(HWND window_handle, 287 UINT message, 288 WPARAM wparam, 289 LPARAM lparam, 290 BOOL* handled) { 291 // We have to prevent WTL from calling ::DefWindowProc() because we do not 292 // want for the IMM (Input Method Manager) to send WM_IME_CHAR messages. 293 *handled = TRUE; 294 295 // At first, update the position of the IME window. 296 imm32_manager_.UpdateImeWindow(window_handle); 297 298 // Retrieve the result string and its attributes of the ongoing composition 299 // and send it to a renderer process. 300 ui::CompositionText composition; 301 if (imm32_manager_.GetResult(window_handle, lparam, &composition.text)) { 302 if (!IsTextInputTypeNone()) 303 GetTextInputClient()->InsertText(composition.text); 304 imm32_manager_.ResetComposition(window_handle); 305 // Fall though and try reading the composition string. 306 // Japanese IMEs send a message containing both GCS_RESULTSTR and 307 // GCS_COMPSTR, which means an ongoing composition has been finished 308 // by the start of another composition. 309 } 310 // Retrieve the composition string and its attributes of the ongoing 311 // composition and send it to a renderer process. 312 if (imm32_manager_.GetComposition(window_handle, lparam, &composition) && 313 !IsTextInputTypeNone()) 314 GetTextInputClient()->SetCompositionText(composition); 315 316 return 0; 317 } 318 319 LRESULT InputMethodWin::OnImeEndComposition(HWND window_handle, 320 UINT message, 321 WPARAM wparam, 322 LPARAM lparam, 323 BOOL* handled) { 324 // Let WTL call ::DefWindowProc() and release its resources. 325 *handled = FALSE; 326 327 composing_window_handle_ = NULL; 328 329 if (!IsTextInputTypeNone() && GetTextInputClient()->HasCompositionText()) 330 GetTextInputClient()->ClearCompositionText(); 331 332 imm32_manager_.ResetComposition(window_handle); 333 imm32_manager_.DestroyImeWindow(window_handle); 334 return 0; 335 } 336 337 LRESULT InputMethodWin::OnImeNotify(UINT message, 338 WPARAM wparam, 339 LPARAM lparam, 340 BOOL* handled) { 341 *handled = FALSE; 342 343 bool previous_state = is_candidate_popup_open_; 344 345 // Update |is_candidate_popup_open_|, whether a candidate window is open. 346 switch (wparam) { 347 case IMN_OPENCANDIDATE: 348 is_candidate_popup_open_ = true; 349 if (!previous_state) 350 OnCandidateWindowShown(); 351 break; 352 case IMN_CLOSECANDIDATE: 353 is_candidate_popup_open_ = false; 354 if (previous_state) 355 OnCandidateWindowHidden(); 356 break; 357 case IMN_CHANGECANDIDATE: 358 // TODO(kochi): The IME API expects this event to notify window size change, 359 // while this may fire more often without window resize. There is no generic 360 // way to get bounds of candidate window. 361 OnCandidateWindowUpdated(); 362 break; 363 } 364 365 return 0; 366 } 367 368 LRESULT InputMethodWin::OnImeRequest(UINT message, 369 WPARAM wparam, 370 LPARAM lparam, 371 BOOL* handled) { 372 *handled = FALSE; 373 374 // Should not receive WM_IME_REQUEST message, if IME is disabled. 375 const ui::TextInputType type = GetTextInputType(); 376 if (type == ui::TEXT_INPUT_TYPE_NONE || 377 type == ui::TEXT_INPUT_TYPE_PASSWORD) { 378 return 0; 379 } 380 381 switch (wparam) { 382 case IMR_RECONVERTSTRING: 383 *handled = TRUE; 384 return OnReconvertString(reinterpret_cast<RECONVERTSTRING*>(lparam)); 385 case IMR_DOCUMENTFEED: 386 *handled = TRUE; 387 return OnDocumentFeed(reinterpret_cast<RECONVERTSTRING*>(lparam)); 388 case IMR_QUERYCHARPOSITION: 389 *handled = TRUE; 390 return OnQueryCharPosition(reinterpret_cast<IMECHARPOSITION*>(lparam)); 391 default: 392 return 0; 393 } 394 } 395 396 LRESULT InputMethodWin::OnDocumentFeed(RECONVERTSTRING* reconv) { 397 ui::TextInputClient* client = GetTextInputClient(); 398 if (!client) 399 return 0; 400 401 gfx::Range text_range; 402 if (!client->GetTextRange(&text_range) || text_range.is_empty()) 403 return 0; 404 405 bool result = false; 406 gfx::Range target_range; 407 if (client->HasCompositionText()) 408 result = client->GetCompositionTextRange(&target_range); 409 410 if (!result || target_range.is_empty()) { 411 if (!client->GetSelectionRange(&target_range) || 412 !target_range.IsValid()) { 413 return 0; 414 } 415 } 416 417 if (!text_range.Contains(target_range)) 418 return 0; 419 420 if (target_range.GetMin() - text_range.start() > kExtraNumberOfChars) 421 text_range.set_start(target_range.GetMin() - kExtraNumberOfChars); 422 423 if (text_range.end() - target_range.GetMax() > kExtraNumberOfChars) 424 text_range.set_end(target_range.GetMax() + kExtraNumberOfChars); 425 426 size_t len = text_range.length(); 427 size_t need_size = sizeof(RECONVERTSTRING) + len * sizeof(WCHAR); 428 429 if (!reconv) 430 return need_size; 431 432 if (reconv->dwSize < need_size) 433 return 0; 434 435 base::string16 text; 436 if (!GetTextInputClient()->GetTextFromRange(text_range, &text)) 437 return 0; 438 DCHECK_EQ(text_range.length(), text.length()); 439 440 reconv->dwVersion = 0; 441 reconv->dwStrLen = len; 442 reconv->dwStrOffset = sizeof(RECONVERTSTRING); 443 reconv->dwCompStrLen = 444 client->HasCompositionText() ? target_range.length() : 0; 445 reconv->dwCompStrOffset = 446 (target_range.GetMin() - text_range.start()) * sizeof(WCHAR); 447 reconv->dwTargetStrLen = target_range.length(); 448 reconv->dwTargetStrOffset = reconv->dwCompStrOffset; 449 450 memcpy((char*)reconv + sizeof(RECONVERTSTRING), 451 text.c_str(), len * sizeof(WCHAR)); 452 453 // According to Microsoft API document, IMR_RECONVERTSTRING and 454 // IMR_DOCUMENTFEED should return reconv, but some applications return 455 // need_size. 456 return reinterpret_cast<LRESULT>(reconv); 457 } 458 459 LRESULT InputMethodWin::OnReconvertString(RECONVERTSTRING* reconv) { 460 ui::TextInputClient* client = GetTextInputClient(); 461 if (!client) 462 return 0; 463 464 // If there is a composition string already, we don't allow reconversion. 465 if (client->HasCompositionText()) 466 return 0; 467 468 gfx::Range text_range; 469 if (!client->GetTextRange(&text_range) || text_range.is_empty()) 470 return 0; 471 472 gfx::Range selection_range; 473 if (!client->GetSelectionRange(&selection_range) || 474 selection_range.is_empty()) { 475 return 0; 476 } 477 478 DCHECK(text_range.Contains(selection_range)); 479 480 size_t len = selection_range.length(); 481 size_t need_size = sizeof(RECONVERTSTRING) + len * sizeof(WCHAR); 482 483 if (!reconv) 484 return need_size; 485 486 if (reconv->dwSize < need_size) 487 return 0; 488 489 // TODO(penghuang): Return some extra context to help improve IME's 490 // reconversion accuracy. 491 base::string16 text; 492 if (!GetTextInputClient()->GetTextFromRange(selection_range, &text)) 493 return 0; 494 DCHECK_EQ(selection_range.length(), text.length()); 495 496 reconv->dwVersion = 0; 497 reconv->dwStrLen = len; 498 reconv->dwStrOffset = sizeof(RECONVERTSTRING); 499 reconv->dwCompStrLen = len; 500 reconv->dwCompStrOffset = 0; 501 reconv->dwTargetStrLen = len; 502 reconv->dwTargetStrOffset = 0; 503 504 memcpy(reinterpret_cast<char*>(reconv) + sizeof(RECONVERTSTRING), 505 text.c_str(), len * sizeof(WCHAR)); 506 507 // According to Microsoft API document, IMR_RECONVERTSTRING and 508 // IMR_DOCUMENTFEED should return reconv, but some applications return 509 // need_size. 510 return reinterpret_cast<LRESULT>(reconv); 511 } 512 513 LRESULT InputMethodWin::OnQueryCharPosition(IMECHARPOSITION* char_positon) { 514 if (!char_positon) 515 return 0; 516 517 if (char_positon->dwSize < sizeof(IMECHARPOSITION)) 518 return 0; 519 520 ui::TextInputClient* client = GetTextInputClient(); 521 if (!client) 522 return 0; 523 524 // Tentatively assume that the returned value from |client| is DIP (Density 525 // Independent Pixel). See the comment in text_input_client.h and 526 // http://crbug.com/360334. 527 gfx::Rect dip_rect; 528 if (client->HasCompositionText()) { 529 if (!client->GetCompositionCharacterBounds(char_positon->dwCharPos, 530 &dip_rect)) { 531 return 0; 532 } 533 } else { 534 // If there is no composition and the first character is queried, returns 535 // the caret bounds. This behavior is the same to that of RichEdit control. 536 if (char_positon->dwCharPos != 0) 537 return 0; 538 dip_rect = client->GetCaretBounds(); 539 } 540 const gfx::Rect rect = gfx::win::DIPToScreenRect(dip_rect); 541 542 char_positon->pt.x = rect.x(); 543 char_positon->pt.y = rect.y(); 544 char_positon->cLineHeight = rect.height(); 545 return 1; // returns non-zero value when succeeded. 546 } 547 548 HWND InputMethodWin::GetAttachedWindowHandle( 549 const TextInputClient* text_input_client) const { 550 // On Aura environment, we can assume that |toplevel_window_handle_| always 551 // represents the valid top-level window handle because each top-level window 552 // is responsible for lifecycle management of corresponding InputMethod 553 // instance. 554 return toplevel_window_handle_; 555 } 556 557 bool InputMethodWin::IsWindowFocused(const TextInputClient* client) const { 558 if (!client) 559 return false; 560 HWND attached_window_handle = GetAttachedWindowHandle(client); 561 // When Aura is enabled, |attached_window_handle| should always be a top-level 562 // window. So we can safely assume that |attached_window_handle| is ready for 563 // receiving keyboard input as long as it is an active window. This works well 564 // even when the |attached_window_handle| becomes active but has not received 565 // WM_FOCUS yet. 566 return attached_window_handle && GetActiveWindow() == attached_window_handle; 567 } 568 569 bool InputMethodWin::DispatchFabricatedKeyEvent(const ui::KeyEvent& event) { 570 if (event.is_char()) { 571 if (GetTextInputClient()) { 572 GetTextInputClient()->InsertChar(event.key_code(), 573 ui::GetModifiersFromKeyState()); 574 return true; 575 } 576 } 577 return DispatchKeyEventPostIME(event); 578 } 579 580 void InputMethodWin::ConfirmCompositionText() { 581 if (composing_window_handle_) 582 imm32_manager_.CleanupComposition(composing_window_handle_); 583 584 if (!IsTextInputTypeNone()) { 585 // Though above line should confirm the client's composition text by sending 586 // a result text to us, in case the input method and the client are in 587 // inconsistent states, we check the client's composition state again. 588 if (GetTextInputClient()->HasCompositionText()) 589 GetTextInputClient()->ConfirmCompositionText(); 590 } 591 } 592 593 void InputMethodWin::UpdateIMEState() { 594 // Use switch here in case we are going to add more text input types. 595 // We disable input method in password field. 596 const HWND window_handle = GetAttachedWindowHandle(GetTextInputClient()); 597 const TextInputType text_input_type = GetTextInputType(); 598 const TextInputMode text_input_mode = GetTextInputMode(); 599 switch (text_input_type) { 600 case ui::TEXT_INPUT_TYPE_NONE: 601 case ui::TEXT_INPUT_TYPE_PASSWORD: 602 imm32_manager_.DisableIME(window_handle); 603 enabled_ = false; 604 break; 605 default: 606 imm32_manager_.EnableIME(window_handle); 607 enabled_ = true; 608 break; 609 } 610 611 imm32_manager_.SetTextInputMode(window_handle, text_input_mode); 612 tsf_inputscope::SetInputScopeForTsfUnawareWindow( 613 window_handle, text_input_type, text_input_mode); 614 } 615 616 } // namespace ui 617