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.
5 #include "ui/base/ime/input_method_win.h"
7 #include "base/basictypes.h"
8 #include "base/profiler/scoped_tracker.h"
9 #include "ui/base/ime/text_input_client.h"
10 #include "ui/base/ime/win/tsf_input_scope.h"
11 #include "ui/events/event.h"
12 #include "ui/events/event_constants.h"
13 #include "ui/events/event_utils.h"
14 #include "ui/events/keycodes/keyboard_codes.h"
15 #include "ui/gfx/win/dpi.h"
16 #include "ui/gfx/win/hwnd_util.h"
21 // Extra number of chars before and after selection (or composition) range which
22 // is returned to IME for improving conversion accuracy.
23 static const size_t kExtraNumberOfChars
= 20;
27 InputMethodWin::InputMethodWin(internal::InputMethodDelegate
* delegate
,
28 HWND toplevel_window_handle
)
29 : toplevel_window_handle_(toplevel_window_handle
),
30 pending_requested_direction_(base::i18n::UNKNOWN_DIRECTION
),
31 accept_carriage_return_(false),
34 is_candidate_popup_open_(false),
35 composing_window_handle_(NULL
),
36 default_input_language_initialized_(false) {
37 SetDelegate(delegate
);
40 void InputMethodWin::OnFocus() {
41 InputMethodBase::OnFocus();
42 if (GetTextInputClient())
46 void InputMethodWin::OnBlur() {
47 ConfirmCompositionText();
48 // Gets the focused text input client before calling parent's OnBlur() because
49 // it will cause GetTextInputClient() returns NULL.
50 ui::TextInputClient
* client
= GetTextInputClient();
51 InputMethodBase::OnBlur();
56 bool InputMethodWin::OnUntranslatedIMEMessage(
57 const base::NativeEvent
& event
,
58 InputMethod::NativeEventResult
* result
) {
59 LRESULT original_result
= 0;
62 if (!default_input_language_initialized_
) {
63 // Gets the initial input locale.
64 OnInputLocaleChanged();
67 switch (event
.message
) {
68 case WM_IME_SETCONTEXT
:
69 original_result
= OnImeSetContext(
70 event
.hwnd
, event
.message
, event
.wParam
, event
.lParam
, &handled
);
72 case WM_IME_STARTCOMPOSITION
:
73 original_result
= OnImeStartComposition(
74 event
.hwnd
, event
.message
, event
.wParam
, event
.lParam
, &handled
);
76 case WM_IME_COMPOSITION
:
77 original_result
= OnImeComposition(
78 event
.hwnd
, event
.message
, event
.wParam
, event
.lParam
, &handled
);
80 case WM_IME_ENDCOMPOSITION
:
81 original_result
= OnImeEndComposition(
82 event
.hwnd
, event
.message
, event
.wParam
, event
.lParam
, &handled
);
85 original_result
= OnImeRequest(
86 event
.message
, event
.wParam
, event
.lParam
, &handled
);
90 original_result
= OnChar(
91 event
.hwnd
, event
.message
, event
.wParam
, event
.lParam
, &handled
);
94 original_result
= OnImeNotify(
95 event
.message
, event
.wParam
, event
.lParam
, &handled
);
98 NOTREACHED() << "Unknown IME message:" << event
.message
;
102 *result
= original_result
;
106 bool InputMethodWin::DispatchKeyEvent(const ui::KeyEvent
& event
) {
107 if (!event
.HasNativeEvent())
108 return DispatchFabricatedKeyEvent(event
);
110 const base::NativeEvent
& native_key_event
= event
.native_event();
111 if (native_key_event
.message
== WM_CHAR
) {
113 OnChar(native_key_event
.hwnd
, native_key_event
.message
,
114 native_key_event
.wParam
, native_key_event
.lParam
, &handled
);
115 return !!handled
; // Don't send WM_CHAR for post event processing.
117 // Handles ctrl-shift key to change text direction and layout alignment.
118 if (ui::IMM32Manager::IsRTLKeyboardLayoutInstalled() &&
119 !IsTextInputTypeNone()) {
120 // TODO: shouldn't need to generate a KeyEvent here.
121 const ui::KeyEvent
key(native_key_event
);
122 ui::KeyboardCode code
= key
.key_code();
123 if (key
.type() == ui::ET_KEY_PRESSED
) {
124 if (code
== ui::VKEY_SHIFT
) {
125 base::i18n::TextDirection dir
;
126 if (ui::IMM32Manager::IsCtrlShiftPressed(&dir
))
127 pending_requested_direction_
= dir
;
128 } else if (code
!= ui::VKEY_CONTROL
) {
129 pending_requested_direction_
= base::i18n::UNKNOWN_DIRECTION
;
131 } else if (key
.type() == ui::ET_KEY_RELEASED
&&
132 (code
== ui::VKEY_SHIFT
|| code
== ui::VKEY_CONTROL
) &&
133 pending_requested_direction_
!= base::i18n::UNKNOWN_DIRECTION
) {
134 GetTextInputClient()->ChangeTextDirectionAndLayoutAlignment(
135 pending_requested_direction_
);
136 pending_requested_direction_
= base::i18n::UNKNOWN_DIRECTION
;
140 return DispatchKeyEventPostIME(event
);
143 void InputMethodWin::OnTextInputTypeChanged(const TextInputClient
* client
) {
144 if (!IsTextInputClientFocused(client
) || !IsWindowFocused(client
))
146 imm32_manager_
.CancelIME(toplevel_window_handle_
);
150 void InputMethodWin::OnCaretBoundsChanged(const TextInputClient
* client
) {
151 if (!enabled_
|| !IsTextInputClientFocused(client
) ||
152 !IsWindowFocused(client
)) {
155 // The current text input type should not be NONE if |client| is focused.
156 DCHECK(!IsTextInputTypeNone());
157 // Tentatively assume that the returned value is DIP (Density Independent
158 // Pixel). See the comment in text_input_client.h and http://crbug.com/360334.
159 const gfx::Rect
dip_screen_bounds(GetTextInputClient()->GetCaretBounds());
160 const gfx::Rect screen_bounds
= gfx::win::DIPToScreenRect(dip_screen_bounds
);
162 HWND attached_window
= toplevel_window_handle_
;
163 // TODO(ime): see comment in TextInputClient::GetCaretBounds(), this
164 // conversion shouldn't be necessary.
166 GetClientRect(attached_window
, &r
);
167 POINT window_point
= { screen_bounds
.x(), screen_bounds
.y() };
168 ScreenToClient(attached_window
, &window_point
);
169 gfx::Rect
caret_rect(gfx::Point(window_point
.x
, window_point
.y
),
170 screen_bounds
.size());
171 imm32_manager_
.UpdateCaretRect(attached_window
, caret_rect
);
174 void InputMethodWin::CancelComposition(const TextInputClient
* client
) {
175 if (enabled_
&& IsTextInputClientFocused(client
))
176 imm32_manager_
.CancelIME(toplevel_window_handle_
);
179 void InputMethodWin::OnInputLocaleChanged() {
180 default_input_language_initialized_
= true;
181 active_
= imm32_manager_
.SetInputLanguage();
182 locale_
= imm32_manager_
.GetInputLanguageName();
183 OnInputMethodChanged();
186 std::string
InputMethodWin::GetInputLocale() {
190 bool InputMethodWin::IsActive() {
194 bool InputMethodWin::IsCandidatePopupOpen() const {
195 return is_candidate_popup_open_
;
198 void InputMethodWin::OnWillChangeFocusedClient(TextInputClient
* focused_before
,
199 TextInputClient
* focused
) {
200 if (IsWindowFocused(focused_before
))
201 ConfirmCompositionText();
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
);
213 // Force to update caret bounds, in case the client thinks that the caret
214 // bounds has not changed.
215 OnCaretBoundsChanged(focused
);
217 if (focused_before
!= focused
)
218 accept_carriage_return_
= false;
221 LRESULT
InputMethodWin::OnChar(HWND window_handle
,
226 // TODO(vadimt): Remove ScopedTracker below once crbug.com/440919 is fixed.
227 tracked_objects::ScopedTracker
tracking_profile(
228 FROM_HERE_WITH_EXPLICIT_FUNCTION("440919 InputMethodWin::OnChar"));
232 // We need to send character events to the focused text input client event if
233 // its text input type is ui::TEXT_INPUT_TYPE_NONE.
234 if (GetTextInputClient()) {
235 const base::char16 kCarriageReturn
= L
'\r';
236 const base::char16 ch
= static_cast<base::char16
>(wparam
);
237 // A mask to determine the previous key state from |lparam|. The value is 1
238 // if the key is down before the message is sent, or it is 0 if the key is
240 const uint32 kPrevKeyDownBit
= 0x40000000;
241 if (ch
== kCarriageReturn
&& !(lparam
& kPrevKeyDownBit
))
242 accept_carriage_return_
= true;
243 // Conditionally ignore '\r' events to work around crbug.com/319100.
244 // TODO(yukawa, IME): Figure out long-term solution.
245 if (ch
!= kCarriageReturn
|| accept_carriage_return_
)
246 GetTextInputClient()->InsertChar(ch
, ui::GetModifiersFromKeyState());
249 // Explicitly show the system menu at a good location on [Alt]+[Space].
250 // Note: Setting |handled| to FALSE for DefWindowProc triggering of the system
251 // menu causes undesirable titlebar artifacts in the classic theme.
252 if (message
== WM_SYSCHAR
&& wparam
== VK_SPACE
)
253 gfx::ShowSystemMenu(window_handle
);
258 LRESULT
InputMethodWin::OnImeSetContext(HWND window_handle
,
263 // TODO(vadimt): Remove ScopedTracker below once crbug.com/440919 is fixed.
264 tracked_objects::ScopedTracker
tracking_profile(
265 FROM_HERE_WITH_EXPLICIT_FUNCTION(
266 "440919 InputMethodWin::OnImeSetContext"));
269 imm32_manager_
.CreateImeWindow(window_handle
);
271 OnInputMethodChanged();
272 return imm32_manager_
.SetImeWindowStyle(
273 window_handle
, message
, wparam
, lparam
, handled
);
276 LRESULT
InputMethodWin::OnImeStartComposition(HWND window_handle
,
281 // TODO(vadimt): Remove ScopedTracker below once crbug.com/440919 is fixed.
282 tracked_objects::ScopedTracker
tracking_profile(
283 FROM_HERE_WITH_EXPLICIT_FUNCTION(
284 "440919 InputMethodWin::OnImeStartComposition"));
286 // We have to prevent WTL from calling ::DefWindowProc() because the function
287 // calls ::ImmSetCompositionWindow() and ::ImmSetCandidateWindow() to
288 // over-write the position of IME windows.
291 // Reset the composition status and create IME windows.
292 composing_window_handle_
= window_handle
;
293 imm32_manager_
.CreateImeWindow(window_handle
);
294 imm32_manager_
.ResetComposition(window_handle
);
298 LRESULT
InputMethodWin::OnImeComposition(HWND window_handle
,
303 // TODO(vadimt): Remove ScopedTracker below once crbug.com/440919 is fixed.
304 tracked_objects::ScopedTracker
tracking_profile(
305 FROM_HERE_WITH_EXPLICIT_FUNCTION(
306 "440919 InputMethodWin::OnImeComposition"));
308 // We have to prevent WTL from calling ::DefWindowProc() because we do not
309 // want for the IMM (Input Method Manager) to send WM_IME_CHAR messages.
312 // At first, update the position of the IME window.
313 imm32_manager_
.UpdateImeWindow(window_handle
);
315 // Retrieve the result string and its attributes of the ongoing composition
316 // and send it to a renderer process.
317 ui::CompositionText composition
;
318 if (imm32_manager_
.GetResult(window_handle
, lparam
, &composition
.text
)) {
319 if (!IsTextInputTypeNone())
320 GetTextInputClient()->InsertText(composition
.text
);
321 imm32_manager_
.ResetComposition(window_handle
);
322 // Fall though and try reading the composition string.
323 // Japanese IMEs send a message containing both GCS_RESULTSTR and
324 // GCS_COMPSTR, which means an ongoing composition has been finished
325 // by the start of another composition.
327 // Retrieve the composition string and its attributes of the ongoing
328 // composition and send it to a renderer process.
329 if (imm32_manager_
.GetComposition(window_handle
, lparam
, &composition
) &&
330 !IsTextInputTypeNone())
331 GetTextInputClient()->SetCompositionText(composition
);
336 LRESULT
InputMethodWin::OnImeEndComposition(HWND window_handle
,
341 // TODO(vadimt): Remove ScopedTracker below once crbug.com/440919 is fixed.
342 tracked_objects::ScopedTracker
tracking_profile(
343 FROM_HERE_WITH_EXPLICIT_FUNCTION(
344 "440919 InputMethodWin::OnImeEndComposition"));
346 // Let WTL call ::DefWindowProc() and release its resources.
349 composing_window_handle_
= NULL
;
351 if (!IsTextInputTypeNone() && GetTextInputClient()->HasCompositionText())
352 GetTextInputClient()->ClearCompositionText();
354 imm32_manager_
.ResetComposition(window_handle
);
355 imm32_manager_
.DestroyImeWindow(window_handle
);
359 LRESULT
InputMethodWin::OnImeNotify(UINT message
,
363 // TODO(vadimt): Remove ScopedTracker below once crbug.com/440919 is fixed.
364 tracked_objects::ScopedTracker
tracking_profile(
365 FROM_HERE_WITH_EXPLICIT_FUNCTION("440919 InputMethodWin::OnImeNotify"));
369 bool previous_state
= is_candidate_popup_open_
;
371 // Update |is_candidate_popup_open_|, whether a candidate window is open.
373 case IMN_OPENCANDIDATE
:
374 is_candidate_popup_open_
= true;
376 OnCandidateWindowShown();
378 case IMN_CLOSECANDIDATE
:
379 is_candidate_popup_open_
= false;
381 OnCandidateWindowHidden();
383 case IMN_CHANGECANDIDATE
:
384 // TODO(kochi): The IME API expects this event to notify window size change,
385 // while this may fire more often without window resize. There is no generic
386 // way to get bounds of candidate window.
387 OnCandidateWindowUpdated();
394 LRESULT
InputMethodWin::OnImeRequest(UINT message
,
398 // TODO(vadimt): Remove ScopedTracker below once crbug.com/440919 is fixed.
399 tracked_objects::ScopedTracker
tracking_profile(
400 FROM_HERE_WITH_EXPLICIT_FUNCTION("440919 InputMethodWin::OnImeRequest"));
404 // Should not receive WM_IME_REQUEST message, if IME is disabled.
405 const ui::TextInputType type
= GetTextInputType();
406 if (type
== ui::TEXT_INPUT_TYPE_NONE
||
407 type
== ui::TEXT_INPUT_TYPE_PASSWORD
) {
412 case IMR_RECONVERTSTRING
:
414 return OnReconvertString(reinterpret_cast<RECONVERTSTRING
*>(lparam
));
415 case IMR_DOCUMENTFEED
:
417 return OnDocumentFeed(reinterpret_cast<RECONVERTSTRING
*>(lparam
));
418 case IMR_QUERYCHARPOSITION
:
420 return OnQueryCharPosition(reinterpret_cast<IMECHARPOSITION
*>(lparam
));
426 LRESULT
InputMethodWin::OnDocumentFeed(RECONVERTSTRING
* reconv
) {
427 ui::TextInputClient
* client
= GetTextInputClient();
431 gfx::Range text_range
;
432 if (!client
->GetTextRange(&text_range
) || text_range
.is_empty())
436 gfx::Range target_range
;
437 if (client
->HasCompositionText())
438 result
= client
->GetCompositionTextRange(&target_range
);
440 if (!result
|| target_range
.is_empty()) {
441 if (!client
->GetSelectionRange(&target_range
) ||
442 !target_range
.IsValid()) {
447 if (!text_range
.Contains(target_range
))
450 if (target_range
.GetMin() - text_range
.start() > kExtraNumberOfChars
)
451 text_range
.set_start(target_range
.GetMin() - kExtraNumberOfChars
);
453 if (text_range
.end() - target_range
.GetMax() > kExtraNumberOfChars
)
454 text_range
.set_end(target_range
.GetMax() + kExtraNumberOfChars
);
456 size_t len
= text_range
.length();
457 size_t need_size
= sizeof(RECONVERTSTRING
) + len
* sizeof(WCHAR
);
462 if (reconv
->dwSize
< need_size
)
466 if (!GetTextInputClient()->GetTextFromRange(text_range
, &text
))
468 DCHECK_EQ(text_range
.length(), text
.length());
470 reconv
->dwVersion
= 0;
471 reconv
->dwStrLen
= len
;
472 reconv
->dwStrOffset
= sizeof(RECONVERTSTRING
);
473 reconv
->dwCompStrLen
=
474 client
->HasCompositionText() ? target_range
.length() : 0;
475 reconv
->dwCompStrOffset
=
476 (target_range
.GetMin() - text_range
.start()) * sizeof(WCHAR
);
477 reconv
->dwTargetStrLen
= target_range
.length();
478 reconv
->dwTargetStrOffset
= reconv
->dwCompStrOffset
;
480 memcpy((char*)reconv
+ sizeof(RECONVERTSTRING
),
481 text
.c_str(), len
* sizeof(WCHAR
));
483 // According to Microsoft API document, IMR_RECONVERTSTRING and
484 // IMR_DOCUMENTFEED should return reconv, but some applications return
486 return reinterpret_cast<LRESULT
>(reconv
);
489 LRESULT
InputMethodWin::OnReconvertString(RECONVERTSTRING
* reconv
) {
490 ui::TextInputClient
* client
= GetTextInputClient();
494 // If there is a composition string already, we don't allow reconversion.
495 if (client
->HasCompositionText())
498 gfx::Range text_range
;
499 if (!client
->GetTextRange(&text_range
) || text_range
.is_empty())
502 gfx::Range selection_range
;
503 if (!client
->GetSelectionRange(&selection_range
) ||
504 selection_range
.is_empty()) {
508 DCHECK(text_range
.Contains(selection_range
));
510 size_t len
= selection_range
.length();
511 size_t need_size
= sizeof(RECONVERTSTRING
) + len
* sizeof(WCHAR
);
516 if (reconv
->dwSize
< need_size
)
519 // TODO(penghuang): Return some extra context to help improve IME's
520 // reconversion accuracy.
522 if (!GetTextInputClient()->GetTextFromRange(selection_range
, &text
))
524 DCHECK_EQ(selection_range
.length(), text
.length());
526 reconv
->dwVersion
= 0;
527 reconv
->dwStrLen
= len
;
528 reconv
->dwStrOffset
= sizeof(RECONVERTSTRING
);
529 reconv
->dwCompStrLen
= len
;
530 reconv
->dwCompStrOffset
= 0;
531 reconv
->dwTargetStrLen
= len
;
532 reconv
->dwTargetStrOffset
= 0;
534 memcpy(reinterpret_cast<char*>(reconv
) + sizeof(RECONVERTSTRING
),
535 text
.c_str(), len
* sizeof(WCHAR
));
537 // According to Microsoft API document, IMR_RECONVERTSTRING and
538 // IMR_DOCUMENTFEED should return reconv, but some applications return
540 return reinterpret_cast<LRESULT
>(reconv
);
543 LRESULT
InputMethodWin::OnQueryCharPosition(IMECHARPOSITION
* char_positon
) {
547 if (char_positon
->dwSize
< sizeof(IMECHARPOSITION
))
550 ui::TextInputClient
* client
= GetTextInputClient();
554 // Tentatively assume that the returned value from |client| is DIP (Density
555 // Independent Pixel). See the comment in text_input_client.h and
556 // http://crbug.com/360334.
558 if (client
->HasCompositionText()) {
559 if (!client
->GetCompositionCharacterBounds(char_positon
->dwCharPos
,
564 // If there is no composition and the first character is queried, returns
565 // the caret bounds. This behavior is the same to that of RichEdit control.
566 if (char_positon
->dwCharPos
!= 0)
568 dip_rect
= client
->GetCaretBounds();
570 const gfx::Rect rect
= gfx::win::DIPToScreenRect(dip_rect
);
572 char_positon
->pt
.x
= rect
.x();
573 char_positon
->pt
.y
= rect
.y();
574 char_positon
->cLineHeight
= rect
.height();
575 return 1; // returns non-zero value when succeeded.
578 bool InputMethodWin::IsWindowFocused(const TextInputClient
* client
) const {
581 // When Aura is enabled, |attached_window_handle| should always be a top-level
582 // window. So we can safely assume that |attached_window_handle| is ready for
583 // receiving keyboard input as long as it is an active window. This works well
584 // even when the |attached_window_handle| becomes active but has not received
586 return toplevel_window_handle_
&&
587 GetActiveWindow() == toplevel_window_handle_
;
590 bool InputMethodWin::DispatchFabricatedKeyEvent(const ui::KeyEvent
& event
) {
591 if (event
.is_char()) {
592 if (GetTextInputClient()) {
593 GetTextInputClient()->InsertChar(
594 static_cast<base::char16
>(event
.key_code()),
595 ui::GetModifiersFromKeyState());
599 return DispatchKeyEventPostIME(event
);
602 void InputMethodWin::ConfirmCompositionText() {
603 if (composing_window_handle_
)
604 imm32_manager_
.CleanupComposition(composing_window_handle_
);
606 if (!IsTextInputTypeNone()) {
607 // Though above line should confirm the client's composition text by sending
608 // a result text to us, in case the input method and the client are in
609 // inconsistent states, we check the client's composition state again.
610 if (GetTextInputClient()->HasCompositionText())
611 GetTextInputClient()->ConfirmCompositionText();
615 void InputMethodWin::UpdateIMEState() {
616 // Use switch here in case we are going to add more text input types.
617 // We disable input method in password field.
618 const HWND window_handle
= toplevel_window_handle_
;
619 const TextInputType text_input_type
= GetTextInputType();
620 const TextInputMode text_input_mode
= GetTextInputMode();
621 switch (text_input_type
) {
622 case ui::TEXT_INPUT_TYPE_NONE
:
623 case ui::TEXT_INPUT_TYPE_PASSWORD
:
624 imm32_manager_
.DisableIME(window_handle
);
628 imm32_manager_
.EnableIME(window_handle
);
633 imm32_manager_
.SetTextInputMode(window_handle
, text_input_mode
);
634 tsf_inputscope::SetInputScopeForTsfUnawareWindow(
635 window_handle
, text_input_type
, text_input_mode
);