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/auto_reset.h"
8 #include "base/basictypes.h"
9 #include "base/profiler/scoped_tracker.h"
10 #include "ui/base/ime/text_input_client.h"
11 #include "ui/base/ime/win/tsf_input_scope.h"
12 #include "ui/events/event.h"
13 #include "ui/events/event_constants.h"
14 #include "ui/events/event_utils.h"
15 #include "ui/events/keycodes/keyboard_codes.h"
16 #include "ui/gfx/win/dpi.h"
17 #include "ui/gfx/win/hwnd_util.h"
22 // Extra number of chars before and after selection (or composition) range which
23 // is returned to IME for improving conversion accuracy.
24 static const size_t kExtraNumberOfChars
= 20;
28 InputMethodWin::InputMethodWin(internal::InputMethodDelegate
* delegate
,
29 HWND toplevel_window_handle
)
30 : toplevel_window_handle_(toplevel_window_handle
),
31 pending_requested_direction_(base::i18n::UNKNOWN_DIRECTION
),
32 accept_carriage_return_(false),
34 is_candidate_popup_open_(false),
35 composing_window_handle_(NULL
),
36 suppress_next_char_(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 switch (event
.message
) {
63 case WM_IME_SETCONTEXT
:
64 original_result
= OnImeSetContext(
65 event
.hwnd
, event
.message
, event
.wParam
, event
.lParam
, &handled
);
67 case WM_IME_STARTCOMPOSITION
:
68 original_result
= OnImeStartComposition(
69 event
.hwnd
, event
.message
, event
.wParam
, event
.lParam
, &handled
);
71 case WM_IME_COMPOSITION
:
72 original_result
= OnImeComposition(
73 event
.hwnd
, event
.message
, event
.wParam
, event
.lParam
, &handled
);
75 case WM_IME_ENDCOMPOSITION
:
76 original_result
= OnImeEndComposition(
77 event
.hwnd
, event
.message
, event
.wParam
, event
.lParam
, &handled
);
80 original_result
= OnImeRequest(
81 event
.message
, event
.wParam
, event
.lParam
, &handled
);
85 original_result
= OnChar(
86 event
.hwnd
, event
.message
, event
.wParam
, event
.lParam
, &handled
);
89 original_result
= OnImeNotify(
90 event
.message
, event
.wParam
, event
.lParam
, &handled
);
93 NOTREACHED() << "Unknown IME message:" << event
.message
;
97 *result
= original_result
;
101 void InputMethodWin::DispatchKeyEvent(ui::KeyEvent
* event
) {
102 if (!event
->HasNativeEvent()) {
103 DispatchFabricatedKeyEvent(event
);
107 const base::NativeEvent
& native_key_event
= event
->native_event();
108 if (native_key_event
.message
== WM_CHAR
) {
110 OnChar(native_key_event
.hwnd
, native_key_event
.message
,
111 native_key_event
.wParam
, native_key_event
.lParam
, &handled
);
113 event
->StopPropagation();
116 // Handles ctrl-shift key to change text direction and layout alignment.
117 if (ui::IMM32Manager::IsRTLKeyboardLayoutInstalled() &&
118 !IsTextInputTypeNone()) {
119 // TODO: shouldn't need to generate a KeyEvent here.
120 const ui::KeyEvent
key(native_key_event
);
121 ui::KeyboardCode code
= key
.key_code();
122 if (key
.type() == ui::ET_KEY_PRESSED
) {
123 if (code
== ui::VKEY_SHIFT
) {
124 base::i18n::TextDirection dir
;
125 if (ui::IMM32Manager::IsCtrlShiftPressed(&dir
))
126 pending_requested_direction_
= dir
;
127 } else if (code
!= ui::VKEY_CONTROL
) {
128 pending_requested_direction_
= base::i18n::UNKNOWN_DIRECTION
;
130 } else if (key
.type() == ui::ET_KEY_RELEASED
&&
131 (code
== ui::VKEY_SHIFT
|| code
== ui::VKEY_CONTROL
) &&
132 pending_requested_direction_
!= base::i18n::UNKNOWN_DIRECTION
) {
133 GetTextInputClient()->ChangeTextDirectionAndLayoutAlignment(
134 pending_requested_direction_
);
135 pending_requested_direction_
= base::i18n::UNKNOWN_DIRECTION
;
139 ui::EventDispatchDetails details
= DispatchKeyEventPostIME(event
);
140 if (!details
.dispatcher_destroyed
)
141 suppress_next_char_
= event
->stopped_propagation();
144 void InputMethodWin::OnTextInputTypeChanged(const TextInputClient
* client
) {
145 if (!IsTextInputClientFocused(client
) || !IsWindowFocused(client
))
147 imm32_manager_
.CancelIME(toplevel_window_handle_
);
151 void InputMethodWin::OnCaretBoundsChanged(const TextInputClient
* client
) {
152 if (!enabled_
|| !IsTextInputClientFocused(client
) ||
153 !IsWindowFocused(client
)) {
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
);
163 HWND attached_window
= toplevel_window_handle_
;
164 // TODO(ime): see comment in TextInputClient::GetCaretBounds(), this
165 // conversion shouldn't be necessary.
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
);
175 void InputMethodWin::CancelComposition(const TextInputClient
* client
) {
176 if (enabled_
&& IsTextInputClientFocused(client
))
177 imm32_manager_
.CancelIME(toplevel_window_handle_
);
180 void InputMethodWin::OnInputLocaleChanged() {
181 // Note: OnInputLocaleChanged() is for crbug.com/168971.
182 OnInputMethodChanged();
185 std::string
InputMethodWin::GetInputLocale() {
186 return imm32_manager_
.GetInputLanguageName();
189 bool InputMethodWin::IsCandidatePopupOpen() const {
190 return is_candidate_popup_open_
;
193 void InputMethodWin::OnWillChangeFocusedClient(TextInputClient
* focused_before
,
194 TextInputClient
* focused
) {
195 if (IsWindowFocused(focused_before
))
196 ConfirmCompositionText();
199 void InputMethodWin::OnDidChangeFocusedClient(
200 TextInputClient
* focused_before
,
201 TextInputClient
* focused
) {
202 if (IsWindowFocused(focused
)) {
203 // Force to update the input type since client's TextInputStateChanged()
204 // function might not be called if text input types before the client loses
205 // focus and after it acquires focus again are the same.
206 OnTextInputTypeChanged(focused
);
208 // Force to update caret bounds, in case the client thinks that the caret
209 // bounds has not changed.
210 OnCaretBoundsChanged(focused
);
212 if (focused_before
!= focused
)
213 accept_carriage_return_
= false;
216 LRESULT
InputMethodWin::OnChar(HWND window_handle
,
221 // TODO(vadimt): Remove ScopedTracker below once crbug.com/440919 is fixed.
222 tracked_objects::ScopedTracker
tracking_profile(
223 FROM_HERE_WITH_EXPLICIT_FUNCTION("440919 InputMethodWin::OnChar"));
227 if (suppress_next_char_
) {
228 suppress_next_char_
= false;
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
);
270 if (system_toplevel_window_focused()) {
271 // Delay initialize the tsf to avoid perf regression.
272 // Loading tsf dll causes some time, so doing it in UpdateIMEState() will
273 // slow down the browser window creation.
274 // See crbug.com/509984.
275 tsf_inputscope::InitializeTsfForInputScopes();
276 tsf_inputscope::SetInputScopeForTsfUnawareWindow(
277 toplevel_window_handle_
, GetTextInputType(), GetTextInputMode());
281 OnInputMethodChanged();
282 return imm32_manager_
.SetImeWindowStyle(
283 window_handle
, message
, wparam
, lparam
, handled
);
286 LRESULT
InputMethodWin::OnImeStartComposition(HWND window_handle
,
291 // TODO(vadimt): Remove ScopedTracker below once crbug.com/440919 is fixed.
292 tracked_objects::ScopedTracker
tracking_profile(
293 FROM_HERE_WITH_EXPLICIT_FUNCTION(
294 "440919 InputMethodWin::OnImeStartComposition"));
296 // We have to prevent WTL from calling ::DefWindowProc() because the function
297 // calls ::ImmSetCompositionWindow() and ::ImmSetCandidateWindow() to
298 // over-write the position of IME windows.
301 // Reset the composition status and create IME windows.
302 composing_window_handle_
= window_handle
;
303 imm32_manager_
.CreateImeWindow(window_handle
);
304 imm32_manager_
.ResetComposition(window_handle
);
308 LRESULT
InputMethodWin::OnImeComposition(HWND window_handle
,
313 // TODO(vadimt): Remove ScopedTracker below once crbug.com/440919 is fixed.
314 tracked_objects::ScopedTracker
tracking_profile(
315 FROM_HERE_WITH_EXPLICIT_FUNCTION(
316 "440919 InputMethodWin::OnImeComposition"));
318 // We have to prevent WTL from calling ::DefWindowProc() because we do not
319 // want for the IMM (Input Method Manager) to send WM_IME_CHAR messages.
322 // At first, update the position of the IME window.
323 imm32_manager_
.UpdateImeWindow(window_handle
);
325 // Retrieve the result string and its attributes of the ongoing composition
326 // and send it to a renderer process.
327 ui::CompositionText composition
;
328 if (imm32_manager_
.GetResult(window_handle
, lparam
, &composition
.text
)) {
329 if (!IsTextInputTypeNone())
330 GetTextInputClient()->InsertText(composition
.text
);
331 imm32_manager_
.ResetComposition(window_handle
);
332 // Fall though and try reading the composition string.
333 // Japanese IMEs send a message containing both GCS_RESULTSTR and
334 // GCS_COMPSTR, which means an ongoing composition has been finished
335 // by the start of another composition.
337 // Retrieve the composition string and its attributes of the ongoing
338 // composition and send it to a renderer process.
339 if (imm32_manager_
.GetComposition(window_handle
, lparam
, &composition
) &&
340 !IsTextInputTypeNone())
341 GetTextInputClient()->SetCompositionText(composition
);
346 LRESULT
InputMethodWin::OnImeEndComposition(HWND window_handle
,
351 // TODO(vadimt): Remove ScopedTracker below once crbug.com/440919 is fixed.
352 tracked_objects::ScopedTracker
tracking_profile(
353 FROM_HERE_WITH_EXPLICIT_FUNCTION(
354 "440919 InputMethodWin::OnImeEndComposition"));
356 // Let WTL call ::DefWindowProc() and release its resources.
359 composing_window_handle_
= NULL
;
361 if (!IsTextInputTypeNone() && GetTextInputClient()->HasCompositionText())
362 GetTextInputClient()->ClearCompositionText();
364 imm32_manager_
.ResetComposition(window_handle
);
365 imm32_manager_
.DestroyImeWindow(window_handle
);
369 LRESULT
InputMethodWin::OnImeNotify(UINT message
,
373 // TODO(vadimt): Remove ScopedTracker below once crbug.com/440919 is fixed.
374 tracked_objects::ScopedTracker
tracking_profile(
375 FROM_HERE_WITH_EXPLICIT_FUNCTION("440919 InputMethodWin::OnImeNotify"));
379 // Update |is_candidate_popup_open_|, whether a candidate window is open.
381 case IMN_OPENCANDIDATE
:
382 is_candidate_popup_open_
= true;
384 case IMN_CLOSECANDIDATE
:
385 is_candidate_popup_open_
= false;
392 LRESULT
InputMethodWin::OnImeRequest(UINT message
,
396 // TODO(vadimt): Remove ScopedTracker below once crbug.com/440919 is fixed.
397 tracked_objects::ScopedTracker
tracking_profile(
398 FROM_HERE_WITH_EXPLICIT_FUNCTION("440919 InputMethodWin::OnImeRequest"));
402 // Should not receive WM_IME_REQUEST message, if IME is disabled.
403 const ui::TextInputType type
= GetTextInputType();
404 if (type
== ui::TEXT_INPUT_TYPE_NONE
||
405 type
== ui::TEXT_INPUT_TYPE_PASSWORD
) {
410 case IMR_RECONVERTSTRING
:
412 return OnReconvertString(reinterpret_cast<RECONVERTSTRING
*>(lparam
));
413 case IMR_DOCUMENTFEED
:
415 return OnDocumentFeed(reinterpret_cast<RECONVERTSTRING
*>(lparam
));
416 case IMR_QUERYCHARPOSITION
:
418 return OnQueryCharPosition(reinterpret_cast<IMECHARPOSITION
*>(lparam
));
424 LRESULT
InputMethodWin::OnDocumentFeed(RECONVERTSTRING
* reconv
) {
425 ui::TextInputClient
* client
= GetTextInputClient();
429 gfx::Range text_range
;
430 if (!client
->GetTextRange(&text_range
) || text_range
.is_empty())
434 gfx::Range target_range
;
435 if (client
->HasCompositionText())
436 result
= client
->GetCompositionTextRange(&target_range
);
438 if (!result
|| target_range
.is_empty()) {
439 if (!client
->GetSelectionRange(&target_range
) ||
440 !target_range
.IsValid()) {
445 if (!text_range
.Contains(target_range
))
448 if (target_range
.GetMin() - text_range
.start() > kExtraNumberOfChars
)
449 text_range
.set_start(target_range
.GetMin() - kExtraNumberOfChars
);
451 if (text_range
.end() - target_range
.GetMax() > kExtraNumberOfChars
)
452 text_range
.set_end(target_range
.GetMax() + kExtraNumberOfChars
);
454 size_t len
= text_range
.length();
455 size_t need_size
= sizeof(RECONVERTSTRING
) + len
* sizeof(WCHAR
);
460 if (reconv
->dwSize
< need_size
)
464 if (!GetTextInputClient()->GetTextFromRange(text_range
, &text
))
466 DCHECK_EQ(text_range
.length(), text
.length());
468 reconv
->dwVersion
= 0;
469 reconv
->dwStrLen
= len
;
470 reconv
->dwStrOffset
= sizeof(RECONVERTSTRING
);
471 reconv
->dwCompStrLen
=
472 client
->HasCompositionText() ? target_range
.length() : 0;
473 reconv
->dwCompStrOffset
=
474 (target_range
.GetMin() - text_range
.start()) * sizeof(WCHAR
);
475 reconv
->dwTargetStrLen
= target_range
.length();
476 reconv
->dwTargetStrOffset
= reconv
->dwCompStrOffset
;
478 memcpy((char*)reconv
+ sizeof(RECONVERTSTRING
),
479 text
.c_str(), len
* sizeof(WCHAR
));
481 // According to Microsoft API document, IMR_RECONVERTSTRING and
482 // IMR_DOCUMENTFEED should return reconv, but some applications return
484 return reinterpret_cast<LRESULT
>(reconv
);
487 LRESULT
InputMethodWin::OnReconvertString(RECONVERTSTRING
* reconv
) {
488 ui::TextInputClient
* client
= GetTextInputClient();
492 // If there is a composition string already, we don't allow reconversion.
493 if (client
->HasCompositionText())
496 gfx::Range text_range
;
497 if (!client
->GetTextRange(&text_range
) || text_range
.is_empty())
500 gfx::Range selection_range
;
501 if (!client
->GetSelectionRange(&selection_range
) ||
502 selection_range
.is_empty()) {
506 DCHECK(text_range
.Contains(selection_range
));
508 size_t len
= selection_range
.length();
509 size_t need_size
= sizeof(RECONVERTSTRING
) + len
* sizeof(WCHAR
);
514 if (reconv
->dwSize
< need_size
)
517 // TODO(penghuang): Return some extra context to help improve IME's
518 // reconversion accuracy.
520 if (!GetTextInputClient()->GetTextFromRange(selection_range
, &text
))
522 DCHECK_EQ(selection_range
.length(), text
.length());
524 reconv
->dwVersion
= 0;
525 reconv
->dwStrLen
= len
;
526 reconv
->dwStrOffset
= sizeof(RECONVERTSTRING
);
527 reconv
->dwCompStrLen
= len
;
528 reconv
->dwCompStrOffset
= 0;
529 reconv
->dwTargetStrLen
= len
;
530 reconv
->dwTargetStrOffset
= 0;
532 memcpy(reinterpret_cast<char*>(reconv
) + sizeof(RECONVERTSTRING
),
533 text
.c_str(), len
* sizeof(WCHAR
));
535 // According to Microsoft API document, IMR_RECONVERTSTRING and
536 // IMR_DOCUMENTFEED should return reconv, but some applications return
538 return reinterpret_cast<LRESULT
>(reconv
);
541 LRESULT
InputMethodWin::OnQueryCharPosition(IMECHARPOSITION
* char_positon
) {
545 if (char_positon
->dwSize
< sizeof(IMECHARPOSITION
))
548 ui::TextInputClient
* client
= GetTextInputClient();
552 // Tentatively assume that the returned value from |client| is DIP (Density
553 // Independent Pixel). See the comment in text_input_client.h and
554 // http://crbug.com/360334.
556 if (client
->HasCompositionText()) {
557 if (!client
->GetCompositionCharacterBounds(char_positon
->dwCharPos
,
562 // If there is no composition and the first character is queried, returns
563 // the caret bounds. This behavior is the same to that of RichEdit control.
564 if (char_positon
->dwCharPos
!= 0)
566 dip_rect
= client
->GetCaretBounds();
568 const gfx::Rect rect
= gfx::win::DIPToScreenRect(dip_rect
);
570 char_positon
->pt
.x
= rect
.x();
571 char_positon
->pt
.y
= rect
.y();
572 char_positon
->cLineHeight
= rect
.height();
573 return 1; // returns non-zero value when succeeded.
576 bool InputMethodWin::IsWindowFocused(const TextInputClient
* client
) const {
579 // When Aura is enabled, |attached_window_handle| should always be a top-level
580 // window. So we can safely assume that |attached_window_handle| is ready for
581 // receiving keyboard input as long as it is an active window. This works well
582 // even when the |attached_window_handle| becomes active but has not received
584 return toplevel_window_handle_
&&
585 GetActiveWindow() == toplevel_window_handle_
;
588 void InputMethodWin::DispatchFabricatedKeyEvent(ui::KeyEvent
* event
) {
589 if (event
->is_char()) {
590 if (suppress_next_char_
) {
591 suppress_next_char_
= false;
594 if (GetTextInputClient()) {
595 GetTextInputClient()->InsertChar(
596 static_cast<base::char16
>(event
->key_code()),
597 ui::GetModifiersFromKeyState());
601 ignore_result(DispatchKeyEventPostIME(event
));
604 void InputMethodWin::ConfirmCompositionText() {
605 if (composing_window_handle_
)
606 imm32_manager_
.CleanupComposition(composing_window_handle_
);
608 if (!IsTextInputTypeNone()) {
609 // Though above line should confirm the client's composition text by sending
610 // a result text to us, in case the input method and the client are in
611 // inconsistent states, we check the client's composition state again.
612 if (GetTextInputClient()->HasCompositionText())
613 GetTextInputClient()->ConfirmCompositionText();
617 void InputMethodWin::UpdateIMEState() {
618 // Use switch here in case we are going to add more text input types.
619 // We disable input method in password field.
620 const HWND window_handle
= toplevel_window_handle_
;
621 const TextInputType text_input_type
= GetTextInputType();
622 const TextInputMode text_input_mode
= GetTextInputMode();
623 switch (text_input_type
) {
624 case ui::TEXT_INPUT_TYPE_NONE
:
625 case ui::TEXT_INPUT_TYPE_PASSWORD
:
626 imm32_manager_
.DisableIME(window_handle
);
630 imm32_manager_
.EnableIME(window_handle
);
635 imm32_manager_
.SetTextInputMode(window_handle
, text_input_mode
);
636 tsf_inputscope::SetInputScopeForTsfUnawareWindow(
637 window_handle
, text_input_type
, text_input_mode
);