1 // Copyright 2013 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_auralinux.h"
7 #include "base/auto_reset.h"
8 #include "base/environment.h"
9 #include "ui/base/ime/linux/linux_input_method_context_factory.h"
10 #include "ui/base/ime/text_input_client.h"
11 #include "ui/events/event.h"
15 InputMethodAuraLinux::InputMethodAuraLinux(
16 internal::InputMethodDelegate
* delegate
)
17 : text_input_type_(TEXT_INPUT_TYPE_NONE
),
19 composition_changed_(false),
20 suppress_next_result_(false) {
21 SetDelegate(delegate
);
23 LinuxInputMethodContextFactory::instance()->CreateInputMethodContext(
26 LinuxInputMethodContextFactory::instance()->CreateInputMethodContext(
30 InputMethodAuraLinux::~InputMethodAuraLinux() {
33 LinuxInputMethodContext
* InputMethodAuraLinux::GetContextForTesting(
35 return is_simple
? context_simple_
.get() : context_
.get();
38 // Overriden from InputMethod.
40 bool InputMethodAuraLinux::OnUntranslatedIMEMessage(
41 const base::NativeEvent
& event
,
42 NativeEventResult
* result
) {
46 void InputMethodAuraLinux::DispatchKeyEvent(ui::KeyEvent
* event
) {
47 DCHECK(event
->type() == ET_KEY_PRESSED
|| event
->type() == ET_KEY_RELEASED
);
48 DCHECK(system_toplevel_window_focused());
50 // If no text input client, do nothing.
51 if (!GetTextInputClient()) {
52 ignore_result(DispatchKeyEventPostIME(event
));
56 suppress_next_result_
= false;
57 composition_changed_
= false;
60 bool filtered
= false;
62 base::AutoReset
<bool> flipper(&is_sync_mode_
, true);
63 if (text_input_type_
!= TEXT_INPUT_TYPE_NONE
&&
64 text_input_type_
!= TEXT_INPUT_TYPE_PASSWORD
) {
65 filtered
= context_
->DispatchKeyEvent(*event
);
67 filtered
= context_simple_
->DispatchKeyEvent(*event
);
71 ui::EventDispatchDetails details
;
72 if (event
->type() == ui::ET_KEY_PRESSED
&& filtered
) {
74 details
= DispatchKeyEventPostIME(event
);
75 else if (HasInputMethodResult())
76 details
= SendFakeProcessKeyEvent(event
);
77 if (details
.dispatcher_destroyed
)
79 // If the KEYDOWN is stopped propagation (e.g. triggered an accelerator),
80 // don't InsertChar/InsertText to the input field.
81 if (event
->stopped_propagation() || details
.target_destroyed
) {
86 // Don't send VKEY_PROCESSKEY event if there is no result text or
87 // composition. This is to workaround the weird behavior of IBus with US
88 // keyboard, which mutes the keydown and later fake a new keydown with IME
89 // result in sync mode. In that case, user would expect only
90 // keydown/keypress/keyup event without an initial 229 keydown event.
93 bool should_stop_propagation
= false;
94 // Note: |client| could be NULL because DispatchKeyEventPostIME could have
95 // changed the text input client.
96 TextInputClient
* client
= GetTextInputClient();
97 // Processes the result text before composition for sync mode.
98 if (client
&& !result_text_
.empty()) {
99 if (filtered
&& NeedInsertChar()) {
100 for (const auto ch
: result_text_
)
101 client
->InsertChar(ch
, event
->flags());
103 // If |filtered| is false, that means the IME wants to commit some text
104 // but still release the key to the application. For example, Korean IME
105 // handles ENTER key to confirm its composition but still release it for
106 // the default behavior (e.g. trigger search, etc.)
107 // In such case, don't do InsertChar because a key should only trigger the
108 // keydown event once.
109 client
->InsertText(result_text_
);
111 should_stop_propagation
= true;
114 if (client
&& composition_changed_
&& !IsTextInputTypeNone()) {
115 // If composition changed, does SetComposition if composition is not empty.
116 // And ClearComposition if composition is empty.
117 if (!composition_
.text
.empty())
118 client
->SetCompositionText(composition_
);
119 else if (result_text_
.empty())
120 client
->ClearCompositionText();
121 should_stop_propagation
= true;
124 // Makes sure the cached composition is cleared after committing any text or
125 // cleared composition.
126 if (client
&& !client
->HasCompositionText())
127 composition_
.Clear();
130 details
= DispatchKeyEventPostIME(event
);
131 if (details
.dispatcher_destroyed
) {
132 if (should_stop_propagation
)
133 event
->StopPropagation();
136 if (event
->stopped_propagation() || details
.target_destroyed
) {
138 } else if (event
->type() == ui::ET_KEY_PRESSED
) {
139 // If a key event was not filtered by |context_| or |context_simple_|,
140 // then it means the key event didn't generate any result text. For some
141 // cases, the key event may still generate a valid character, eg. a
142 // control-key event (ctrl-a, return, tab, etc.). We need to send the
143 // character to the focused text input client by calling
144 // TextInputClient::InsertChar().
145 // Note: don't use |client| and use GetTextInputClient() here because
146 // DispatchKeyEventPostIME may cause the current text input client change.
147 base::char16 ch
= event
->GetCharacter();
148 if (ch
&& GetTextInputClient())
149 GetTextInputClient()->InsertChar(ch
, event
->flags());
150 should_stop_propagation
= true;
154 if (should_stop_propagation
)
155 event
->StopPropagation();
158 void InputMethodAuraLinux::UpdateContextFocusState() {
159 bool old_text_input_type
= text_input_type_
;
160 text_input_type_
= GetTextInputType();
162 // We only focus in |context_| when the focus is in a textfield.
163 if (old_text_input_type
!= TEXT_INPUT_TYPE_NONE
&&
164 text_input_type_
== TEXT_INPUT_TYPE_NONE
) {
166 } else if (old_text_input_type
== TEXT_INPUT_TYPE_NONE
&&
167 text_input_type_
!= TEXT_INPUT_TYPE_NONE
) {
171 // |context_simple_| can be used in any textfield, including password box, and
172 // even if the focused text input client's text input type is
173 // ui::TEXT_INPUT_TYPE_NONE.
174 if (GetTextInputClient())
175 context_simple_
->Focus();
177 context_simple_
->Blur();
180 void InputMethodAuraLinux::OnTextInputTypeChanged(
181 const TextInputClient
* client
) {
182 UpdateContextFocusState();
183 InputMethodBase::OnTextInputTypeChanged(client
);
184 // TODO(yoichio): Support inputmode HTML attribute.
187 void InputMethodAuraLinux::OnCaretBoundsChanged(const TextInputClient
* client
) {
188 if (!IsTextInputClientFocused(client
))
190 NotifyTextInputCaretBoundsChanged(client
);
191 context_
->SetCursorLocation(GetTextInputClient()->GetCaretBounds());
194 void InputMethodAuraLinux::CancelComposition(const TextInputClient
* client
) {
195 if (!IsTextInputClientFocused(client
))
200 void InputMethodAuraLinux::ResetContext() {
201 if (!GetTextInputClient())
204 // To prevent any text from being committed when resetting the |context_|;
205 is_sync_mode_
= true;
206 suppress_next_result_
= true;
209 context_simple_
->Reset();
211 // Some input methods may not honour the reset call. Focusing out/in the
212 // |context_| to make sure it gets reset correctly.
213 if (text_input_type_
!= TEXT_INPUT_TYPE_NONE
) {
218 composition_
.Clear();
219 result_text_
.clear();
220 is_sync_mode_
= false;
221 composition_changed_
= false;
224 void InputMethodAuraLinux::OnInputLocaleChanged() {
227 std::string
InputMethodAuraLinux::GetInputLocale() {
231 bool InputMethodAuraLinux::IsCandidatePopupOpen() const {
232 // There seems no way to detect candidate windows or any popups.
236 // Overriden from ui::LinuxInputMethodContextDelegate
238 void InputMethodAuraLinux::OnCommit(const base::string16
& text
) {
239 if (suppress_next_result_
|| !GetTextInputClient()) {
240 suppress_next_result_
= false;
245 // Append the text to the buffer, because commit signal might be fired
246 // multiple times when processing a key event.
247 result_text_
.append(text
);
248 } else if (!IsTextInputTypeNone()) {
249 // If we are not handling key event, do not bother sending text result if
250 // the focused text input client does not support text input.
251 ui::KeyEvent
event(ui::ET_KEY_PRESSED
, ui::VKEY_PROCESSKEY
, 0);
252 ui::EventDispatchDetails details
= SendFakeProcessKeyEvent(&event
);
253 if (details
.dispatcher_destroyed
)
255 if (!event
.stopped_propagation() && !details
.target_destroyed
)
256 GetTextInputClient()->InsertText(text
);
257 composition_
.Clear();
261 void InputMethodAuraLinux::OnPreeditChanged(
262 const CompositionText
& composition_text
) {
263 if (suppress_next_result_
|| IsTextInputTypeNone())
267 if (!composition_
.text
.empty() || !composition_text
.text
.empty())
268 composition_changed_
= true;
270 ui::KeyEvent
event(ui::ET_KEY_PRESSED
, ui::VKEY_PROCESSKEY
, 0);
271 ui::EventDispatchDetails details
= SendFakeProcessKeyEvent(&event
);
272 if (details
.dispatcher_destroyed
)
274 if (!event
.stopped_propagation() && !details
.target_destroyed
)
275 GetTextInputClient()->SetCompositionText(composition_text
);
278 composition_
= composition_text
;
281 void InputMethodAuraLinux::OnPreeditEnd() {
282 if (suppress_next_result_
|| IsTextInputTypeNone())
286 if (!composition_
.text
.empty()) {
287 composition_
.Clear();
288 composition_changed_
= true;
291 TextInputClient
* client
= GetTextInputClient();
292 if (client
&& client
->HasCompositionText()) {
293 ui::KeyEvent
event(ui::ET_KEY_PRESSED
, ui::VKEY_PROCESSKEY
, 0);
294 ui::EventDispatchDetails details
= SendFakeProcessKeyEvent(&event
);
295 if (details
.dispatcher_destroyed
)
297 if (!event
.stopped_propagation() && !details
.target_destroyed
)
298 client
->ClearCompositionText();
300 composition_
.Clear();
304 // Overridden from InputMethodBase.
306 void InputMethodAuraLinux::OnFocus() {
307 InputMethodBase::OnFocus();
308 UpdateContextFocusState();
311 void InputMethodAuraLinux::OnBlur() {
312 ConfirmCompositionText();
313 InputMethodBase::OnBlur();
314 UpdateContextFocusState();
317 void InputMethodAuraLinux::OnWillChangeFocusedClient(
318 TextInputClient
* focused_before
,
319 TextInputClient
* focused
) {
320 ConfirmCompositionText();
323 void InputMethodAuraLinux::OnDidChangeFocusedClient(
324 TextInputClient
* focused_before
,
325 TextInputClient
* focused
) {
326 UpdateContextFocusState();
328 // Force to update caret bounds, in case the View thinks that the caret
329 // bounds has not changed.
330 if (text_input_type_
!= TEXT_INPUT_TYPE_NONE
)
331 OnCaretBoundsChanged(GetTextInputClient());
333 InputMethodBase::OnDidChangeFocusedClient(focused_before
, focused
);
338 bool InputMethodAuraLinux::HasInputMethodResult() {
339 return !result_text_
.empty() || composition_changed_
;
342 bool InputMethodAuraLinux::NeedInsertChar() const {
343 return IsTextInputTypeNone() ||
344 (!composition_changed_
&& composition_
.text
.empty() &&
345 result_text_
.length() == 1);
348 ui::EventDispatchDetails
InputMethodAuraLinux::SendFakeProcessKeyEvent(
349 ui::KeyEvent
* event
) const {
350 KeyEvent
key_event(ui::ET_KEY_PRESSED
, ui::VKEY_PROCESSKEY
, event
->flags());
351 ui::EventDispatchDetails details
= DispatchKeyEventPostIME(&key_event
);
352 if (key_event
.stopped_propagation())
353 event
->StopPropagation();
357 void InputMethodAuraLinux::ConfirmCompositionText() {
358 TextInputClient
* client
= GetTextInputClient();
359 if (client
&& client
->HasCompositionText())
360 client
->ConfirmCompositionText();