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
);
38 // In non-Aura environment, appropriate callbacks to OnFocus() and OnBlur()
39 // are not implemented yet. To work around this limitation, here we use
40 // "always focused" model.
41 // TODO(ime): Fix the caller of OnFocus() and OnBlur() so that appropriate
42 // focus event will be passed.
43 InputMethodBase::OnFocus();
46 void InputMethodWin::Init(bool focused
) {
47 InputMethodBase::Init(focused
);
50 void InputMethodWin::OnFocus() {
51 // Ignore OnFocus event for "always focused" model. See the comment in the
53 // TODO(ime): Implement OnFocus once the callers are fixed.
56 void InputMethodWin::OnBlur() {
57 // Ignore OnBlur event for "always focused" model. See the comment in the
59 // TODO(ime): Implement OnFocus once the callers are fixed.
62 bool InputMethodWin::OnUntranslatedIMEMessage(
63 const base::NativeEvent
& event
,
64 InputMethod::NativeEventResult
* result
) {
65 LRESULT original_result
= 0;
68 if (!default_input_language_initialized_
) {
69 // Gets the initial input locale.
70 OnInputLocaleChanged();
73 switch (event
.message
) {
74 case WM_IME_SETCONTEXT
:
75 original_result
= OnImeSetContext(
76 event
.hwnd
, event
.message
, event
.wParam
, event
.lParam
, &handled
);
78 case WM_IME_STARTCOMPOSITION
:
79 original_result
= OnImeStartComposition(
80 event
.hwnd
, event
.message
, event
.wParam
, event
.lParam
, &handled
);
82 case WM_IME_COMPOSITION
:
83 original_result
= OnImeComposition(
84 event
.hwnd
, event
.message
, event
.wParam
, event
.lParam
, &handled
);
86 case WM_IME_ENDCOMPOSITION
:
87 original_result
= OnImeEndComposition(
88 event
.hwnd
, event
.message
, event
.wParam
, event
.lParam
, &handled
);
91 original_result
= OnImeRequest(
92 event
.message
, event
.wParam
, event
.lParam
, &handled
);
96 original_result
= OnChar(
97 event
.hwnd
, event
.message
, event
.wParam
, event
.lParam
, &handled
);
100 original_result
= OnImeNotify(
101 event
.message
, event
.wParam
, event
.lParam
, &handled
);
104 NOTREACHED() << "Unknown IME message:" << event
.message
;
108 *result
= original_result
;
112 bool InputMethodWin::DispatchKeyEvent(const ui::KeyEvent
& event
) {
113 if (!event
.HasNativeEvent())
114 return DispatchFabricatedKeyEvent(event
);
116 const base::NativeEvent
& native_key_event
= event
.native_event();
117 if (native_key_event
.message
== WM_CHAR
) {
119 OnChar(native_key_event
.hwnd
, native_key_event
.message
,
120 native_key_event
.wParam
, native_key_event
.lParam
, &handled
);
121 return !!handled
; // Don't send WM_CHAR for post event processing.
123 // Handles ctrl-shift key to change text direction and layout alignment.
124 if (ui::IMM32Manager::IsRTLKeyboardLayoutInstalled() &&
125 !IsTextInputTypeNone()) {
126 // TODO: shouldn't need to generate a KeyEvent here.
127 const ui::KeyEvent
key(native_key_event
);
128 ui::KeyboardCode code
= key
.key_code();
129 if (key
.type() == ui::ET_KEY_PRESSED
) {
130 if (code
== ui::VKEY_SHIFT
) {
131 base::i18n::TextDirection dir
;
132 if (ui::IMM32Manager::IsCtrlShiftPressed(&dir
))
133 pending_requested_direction_
= dir
;
134 } else if (code
!= ui::VKEY_CONTROL
) {
135 pending_requested_direction_
= base::i18n::UNKNOWN_DIRECTION
;
137 } else if (key
.type() == ui::ET_KEY_RELEASED
&&
138 (code
== ui::VKEY_SHIFT
|| code
== ui::VKEY_CONTROL
) &&
139 pending_requested_direction_
!= base::i18n::UNKNOWN_DIRECTION
) {
140 GetTextInputClient()->ChangeTextDirectionAndLayoutAlignment(
141 pending_requested_direction_
);
142 pending_requested_direction_
= base::i18n::UNKNOWN_DIRECTION
;
146 return DispatchKeyEventPostIME(event
);
149 void InputMethodWin::OnTextInputTypeChanged(const TextInputClient
* client
) {
150 if (!IsTextInputClientFocused(client
) || !IsWindowFocused(client
))
152 imm32_manager_
.CancelIME(GetAttachedWindowHandle(client
));
156 void InputMethodWin::OnCaretBoundsChanged(const TextInputClient
* client
) {
157 if (!enabled_
|| !IsTextInputClientFocused(client
) ||
158 !IsWindowFocused(client
)) {
161 // The current text input type should not be NONE if |client| is focused.
162 DCHECK(!IsTextInputTypeNone());
163 // Tentatively assume that the returned value is DIP (Density Independent
164 // Pixel). See the comment in text_input_client.h and http://crbug.com/360334.
165 const gfx::Rect
dip_screen_bounds(GetTextInputClient()->GetCaretBounds());
166 const gfx::Rect screen_bounds
= gfx::win::DIPToScreenRect(dip_screen_bounds
);
168 HWND attached_window
= GetAttachedWindowHandle(client
);
169 // TODO(ime): see comment in TextInputClient::GetCaretBounds(), this
170 // conversion shouldn't be necessary.
172 GetClientRect(attached_window
, &r
);
173 POINT window_point
= { screen_bounds
.x(), screen_bounds
.y() };
174 ScreenToClient(attached_window
, &window_point
);
175 gfx::Rect
caret_rect(gfx::Point(window_point
.x
, window_point
.y
),
176 screen_bounds
.size());
177 imm32_manager_
.UpdateCaretRect(attached_window
, caret_rect
);
180 void InputMethodWin::CancelComposition(const TextInputClient
* client
) {
181 if (enabled_
&& IsTextInputClientFocused(client
))
182 imm32_manager_
.CancelIME(GetAttachedWindowHandle(client
));
185 void InputMethodWin::OnInputLocaleChanged() {
186 default_input_language_initialized_
= true;
187 active_
= imm32_manager_
.SetInputLanguage();
188 locale_
= imm32_manager_
.GetInputLanguageName();
189 OnInputMethodChanged();
192 std::string
InputMethodWin::GetInputLocale() {
196 bool InputMethodWin::IsActive() {
200 bool InputMethodWin::IsCandidatePopupOpen() const {
201 return is_candidate_popup_open_
;
204 void InputMethodWin::OnWillChangeFocusedClient(TextInputClient
* focused_before
,
205 TextInputClient
* focused
) {
206 if (IsWindowFocused(focused_before
))
207 ConfirmCompositionText();
210 void InputMethodWin::OnDidChangeFocusedClient(
211 TextInputClient
* focused_before
,
212 TextInputClient
* focused
) {
213 if (IsWindowFocused(focused
)) {
214 // Force to update the input type since client's TextInputStateChanged()
215 // function might not be called if text input types before the client loses
216 // focus and after it acquires focus again are the same.
217 OnTextInputTypeChanged(focused
);
221 // Force to update caret bounds, in case the client thinks that the caret
222 // bounds has not changed.
223 OnCaretBoundsChanged(focused
);
225 if (focused_before
!= focused
)
226 accept_carriage_return_
= false;
229 LRESULT
InputMethodWin::OnChar(HWND window_handle
,
234 // TODO(vadimt): Remove ScopedTracker below once crbug.com/440919 is fixed.
235 tracked_objects::ScopedTracker
tracking_profile(
236 FROM_HERE_WITH_EXPLICIT_FUNCTION("440919 InputMethodWin::OnChar"));
240 // We need to send character events to the focused text input client event if
241 // its text input type is ui::TEXT_INPUT_TYPE_NONE.
242 if (GetTextInputClient()) {
243 const base::char16 kCarriageReturn
= L
'\r';
244 const base::char16 ch
= static_cast<base::char16
>(wparam
);
245 // A mask to determine the previous key state from |lparam|. The value is 1
246 // if the key is down before the message is sent, or it is 0 if the key is
248 const uint32 kPrevKeyDownBit
= 0x40000000;
249 if (ch
== kCarriageReturn
&& !(lparam
& kPrevKeyDownBit
))
250 accept_carriage_return_
= true;
251 // Conditionally ignore '\r' events to work around crbug.com/319100.
252 // TODO(yukawa, IME): Figure out long-term solution.
253 if (ch
!= kCarriageReturn
|| accept_carriage_return_
)
254 GetTextInputClient()->InsertChar(ch
, ui::GetModifiersFromKeyState());
257 // Explicitly show the system menu at a good location on [Alt]+[Space].
258 // Note: Setting |handled| to FALSE for DefWindowProc triggering of the system
259 // menu causes undesirable titlebar artifacts in the classic theme.
260 if (message
== WM_SYSCHAR
&& wparam
== VK_SPACE
)
261 gfx::ShowSystemMenu(window_handle
);
266 LRESULT
InputMethodWin::OnImeSetContext(HWND window_handle
,
271 // TODO(vadimt): Remove ScopedTracker below once crbug.com/440919 is fixed.
272 tracked_objects::ScopedTracker
tracking_profile(
273 FROM_HERE_WITH_EXPLICIT_FUNCTION(
274 "440919 InputMethodWin::OnImeSetContext"));
277 imm32_manager_
.CreateImeWindow(window_handle
);
279 OnInputMethodChanged();
280 return imm32_manager_
.SetImeWindowStyle(
281 window_handle
, message
, wparam
, lparam
, handled
);
284 LRESULT
InputMethodWin::OnImeStartComposition(HWND window_handle
,
289 // TODO(vadimt): Remove ScopedTracker below once crbug.com/440919 is fixed.
290 tracked_objects::ScopedTracker
tracking_profile(
291 FROM_HERE_WITH_EXPLICIT_FUNCTION(
292 "440919 InputMethodWin::OnImeStartComposition"));
294 // We have to prevent WTL from calling ::DefWindowProc() because the function
295 // calls ::ImmSetCompositionWindow() and ::ImmSetCandidateWindow() to
296 // over-write the position of IME windows.
299 // Reset the composition status and create IME windows.
300 composing_window_handle_
= window_handle
;
301 imm32_manager_
.CreateImeWindow(window_handle
);
302 imm32_manager_
.ResetComposition(window_handle
);
306 LRESULT
InputMethodWin::OnImeComposition(HWND window_handle
,
311 // TODO(vadimt): Remove ScopedTracker below once crbug.com/440919 is fixed.
312 tracked_objects::ScopedTracker
tracking_profile(
313 FROM_HERE_WITH_EXPLICIT_FUNCTION(
314 "440919 InputMethodWin::OnImeComposition"));
316 // We have to prevent WTL from calling ::DefWindowProc() because we do not
317 // want for the IMM (Input Method Manager) to send WM_IME_CHAR messages.
320 // At first, update the position of the IME window.
321 imm32_manager_
.UpdateImeWindow(window_handle
);
323 // Retrieve the result string and its attributes of the ongoing composition
324 // and send it to a renderer process.
325 ui::CompositionText composition
;
326 if (imm32_manager_
.GetResult(window_handle
, lparam
, &composition
.text
)) {
327 if (!IsTextInputTypeNone())
328 GetTextInputClient()->InsertText(composition
.text
);
329 imm32_manager_
.ResetComposition(window_handle
);
330 // Fall though and try reading the composition string.
331 // Japanese IMEs send a message containing both GCS_RESULTSTR and
332 // GCS_COMPSTR, which means an ongoing composition has been finished
333 // by the start of another composition.
335 // Retrieve the composition string and its attributes of the ongoing
336 // composition and send it to a renderer process.
337 if (imm32_manager_
.GetComposition(window_handle
, lparam
, &composition
) &&
338 !IsTextInputTypeNone())
339 GetTextInputClient()->SetCompositionText(composition
);
344 LRESULT
InputMethodWin::OnImeEndComposition(HWND window_handle
,
349 // TODO(vadimt): Remove ScopedTracker below once crbug.com/440919 is fixed.
350 tracked_objects::ScopedTracker
tracking_profile(
351 FROM_HERE_WITH_EXPLICIT_FUNCTION(
352 "440919 InputMethodWin::OnImeEndComposition"));
354 // Let WTL call ::DefWindowProc() and release its resources.
357 composing_window_handle_
= NULL
;
359 if (!IsTextInputTypeNone() && GetTextInputClient()->HasCompositionText())
360 GetTextInputClient()->ClearCompositionText();
362 imm32_manager_
.ResetComposition(window_handle
);
363 imm32_manager_
.DestroyImeWindow(window_handle
);
367 LRESULT
InputMethodWin::OnImeNotify(UINT message
,
371 // TODO(vadimt): Remove ScopedTracker below once crbug.com/440919 is fixed.
372 tracked_objects::ScopedTracker
tracking_profile(
373 FROM_HERE_WITH_EXPLICIT_FUNCTION("440919 InputMethodWin::OnImeNotify"));
377 bool previous_state
= is_candidate_popup_open_
;
379 // Update |is_candidate_popup_open_|, whether a candidate window is open.
381 case IMN_OPENCANDIDATE
:
382 is_candidate_popup_open_
= true;
384 OnCandidateWindowShown();
386 case IMN_CLOSECANDIDATE
:
387 is_candidate_popup_open_
= false;
389 OnCandidateWindowHidden();
391 case IMN_CHANGECANDIDATE
:
392 // TODO(kochi): The IME API expects this event to notify window size change,
393 // while this may fire more often without window resize. There is no generic
394 // way to get bounds of candidate window.
395 OnCandidateWindowUpdated();
402 LRESULT
InputMethodWin::OnImeRequest(UINT message
,
406 // TODO(vadimt): Remove ScopedTracker below once crbug.com/440919 is fixed.
407 tracked_objects::ScopedTracker
tracking_profile(
408 FROM_HERE_WITH_EXPLICIT_FUNCTION("440919 InputMethodWin::OnImeRequest"));
412 // Should not receive WM_IME_REQUEST message, if IME is disabled.
413 const ui::TextInputType type
= GetTextInputType();
414 if (type
== ui::TEXT_INPUT_TYPE_NONE
||
415 type
== ui::TEXT_INPUT_TYPE_PASSWORD
) {
420 case IMR_RECONVERTSTRING
:
422 return OnReconvertString(reinterpret_cast<RECONVERTSTRING
*>(lparam
));
423 case IMR_DOCUMENTFEED
:
425 return OnDocumentFeed(reinterpret_cast<RECONVERTSTRING
*>(lparam
));
426 case IMR_QUERYCHARPOSITION
:
428 return OnQueryCharPosition(reinterpret_cast<IMECHARPOSITION
*>(lparam
));
434 LRESULT
InputMethodWin::OnDocumentFeed(RECONVERTSTRING
* reconv
) {
435 ui::TextInputClient
* client
= GetTextInputClient();
439 gfx::Range text_range
;
440 if (!client
->GetTextRange(&text_range
) || text_range
.is_empty())
444 gfx::Range target_range
;
445 if (client
->HasCompositionText())
446 result
= client
->GetCompositionTextRange(&target_range
);
448 if (!result
|| target_range
.is_empty()) {
449 if (!client
->GetSelectionRange(&target_range
) ||
450 !target_range
.IsValid()) {
455 if (!text_range
.Contains(target_range
))
458 if (target_range
.GetMin() - text_range
.start() > kExtraNumberOfChars
)
459 text_range
.set_start(target_range
.GetMin() - kExtraNumberOfChars
);
461 if (text_range
.end() - target_range
.GetMax() > kExtraNumberOfChars
)
462 text_range
.set_end(target_range
.GetMax() + kExtraNumberOfChars
);
464 size_t len
= text_range
.length();
465 size_t need_size
= sizeof(RECONVERTSTRING
) + len
* sizeof(WCHAR
);
470 if (reconv
->dwSize
< need_size
)
474 if (!GetTextInputClient()->GetTextFromRange(text_range
, &text
))
476 DCHECK_EQ(text_range
.length(), text
.length());
478 reconv
->dwVersion
= 0;
479 reconv
->dwStrLen
= len
;
480 reconv
->dwStrOffset
= sizeof(RECONVERTSTRING
);
481 reconv
->dwCompStrLen
=
482 client
->HasCompositionText() ? target_range
.length() : 0;
483 reconv
->dwCompStrOffset
=
484 (target_range
.GetMin() - text_range
.start()) * sizeof(WCHAR
);
485 reconv
->dwTargetStrLen
= target_range
.length();
486 reconv
->dwTargetStrOffset
= reconv
->dwCompStrOffset
;
488 memcpy((char*)reconv
+ sizeof(RECONVERTSTRING
),
489 text
.c_str(), len
* sizeof(WCHAR
));
491 // According to Microsoft API document, IMR_RECONVERTSTRING and
492 // IMR_DOCUMENTFEED should return reconv, but some applications return
494 return reinterpret_cast<LRESULT
>(reconv
);
497 LRESULT
InputMethodWin::OnReconvertString(RECONVERTSTRING
* reconv
) {
498 ui::TextInputClient
* client
= GetTextInputClient();
502 // If there is a composition string already, we don't allow reconversion.
503 if (client
->HasCompositionText())
506 gfx::Range text_range
;
507 if (!client
->GetTextRange(&text_range
) || text_range
.is_empty())
510 gfx::Range selection_range
;
511 if (!client
->GetSelectionRange(&selection_range
) ||
512 selection_range
.is_empty()) {
516 DCHECK(text_range
.Contains(selection_range
));
518 size_t len
= selection_range
.length();
519 size_t need_size
= sizeof(RECONVERTSTRING
) + len
* sizeof(WCHAR
);
524 if (reconv
->dwSize
< need_size
)
527 // TODO(penghuang): Return some extra context to help improve IME's
528 // reconversion accuracy.
530 if (!GetTextInputClient()->GetTextFromRange(selection_range
, &text
))
532 DCHECK_EQ(selection_range
.length(), text
.length());
534 reconv
->dwVersion
= 0;
535 reconv
->dwStrLen
= len
;
536 reconv
->dwStrOffset
= sizeof(RECONVERTSTRING
);
537 reconv
->dwCompStrLen
= len
;
538 reconv
->dwCompStrOffset
= 0;
539 reconv
->dwTargetStrLen
= len
;
540 reconv
->dwTargetStrOffset
= 0;
542 memcpy(reinterpret_cast<char*>(reconv
) + sizeof(RECONVERTSTRING
),
543 text
.c_str(), len
* sizeof(WCHAR
));
545 // According to Microsoft API document, IMR_RECONVERTSTRING and
546 // IMR_DOCUMENTFEED should return reconv, but some applications return
548 return reinterpret_cast<LRESULT
>(reconv
);
551 LRESULT
InputMethodWin::OnQueryCharPosition(IMECHARPOSITION
* char_positon
) {
555 if (char_positon
->dwSize
< sizeof(IMECHARPOSITION
))
558 ui::TextInputClient
* client
= GetTextInputClient();
562 // Tentatively assume that the returned value from |client| is DIP (Density
563 // Independent Pixel). See the comment in text_input_client.h and
564 // http://crbug.com/360334.
566 if (client
->HasCompositionText()) {
567 if (!client
->GetCompositionCharacterBounds(char_positon
->dwCharPos
,
572 // If there is no composition and the first character is queried, returns
573 // the caret bounds. This behavior is the same to that of RichEdit control.
574 if (char_positon
->dwCharPos
!= 0)
576 dip_rect
= client
->GetCaretBounds();
578 const gfx::Rect rect
= gfx::win::DIPToScreenRect(dip_rect
);
580 char_positon
->pt
.x
= rect
.x();
581 char_positon
->pt
.y
= rect
.y();
582 char_positon
->cLineHeight
= rect
.height();
583 return 1; // returns non-zero value when succeeded.
586 HWND
InputMethodWin::GetAttachedWindowHandle(
587 const TextInputClient
* text_input_client
) const {
588 // On Aura environment, we can assume that |toplevel_window_handle_| always
589 // represents the valid top-level window handle because each top-level window
590 // is responsible for lifecycle management of corresponding InputMethod
592 return toplevel_window_handle_
;
595 bool InputMethodWin::IsWindowFocused(const TextInputClient
* client
) const {
598 HWND attached_window_handle
= GetAttachedWindowHandle(client
);
599 // When Aura is enabled, |attached_window_handle| should always be a top-level
600 // window. So we can safely assume that |attached_window_handle| is ready for
601 // receiving keyboard input as long as it is an active window. This works well
602 // even when the |attached_window_handle| becomes active but has not received
604 return attached_window_handle
&& GetActiveWindow() == attached_window_handle
;
607 bool InputMethodWin::DispatchFabricatedKeyEvent(const ui::KeyEvent
& event
) {
608 if (event
.is_char()) {
609 if (GetTextInputClient()) {
610 GetTextInputClient()->InsertChar(
611 static_cast<base::char16
>(event
.key_code()),
612 ui::GetModifiersFromKeyState());
616 return DispatchKeyEventPostIME(event
);
619 void InputMethodWin::ConfirmCompositionText() {
620 if (composing_window_handle_
)
621 imm32_manager_
.CleanupComposition(composing_window_handle_
);
623 if (!IsTextInputTypeNone()) {
624 // Though above line should confirm the client's composition text by sending
625 // a result text to us, in case the input method and the client are in
626 // inconsistent states, we check the client's composition state again.
627 if (GetTextInputClient()->HasCompositionText())
628 GetTextInputClient()->ConfirmCompositionText();
632 void InputMethodWin::UpdateIMEState() {
633 // Use switch here in case we are going to add more text input types.
634 // We disable input method in password field.
635 const HWND window_handle
= GetAttachedWindowHandle(GetTextInputClient());
636 const TextInputType text_input_type
= GetTextInputType();
637 const TextInputMode text_input_mode
= GetTextInputMode();
638 switch (text_input_type
) {
639 case ui::TEXT_INPUT_TYPE_NONE
:
640 case ui::TEXT_INPUT_TYPE_PASSWORD
:
641 imm32_manager_
.DisableIME(window_handle
);
645 imm32_manager_
.EnableIME(window_handle
);
650 imm32_manager_
.SetTextInputMode(window_handle
, text_input_mode
);
651 tsf_inputscope::SetInputScopeForTsfUnawareWindow(
652 window_handle
, text_input_type
, text_input_mode
);