blob: 97ce8ea79b554a36abd06fcd7334d6028b734e52 [file] [log] [blame]
// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ui/base/ime/input_method_auralinux.h"
#include "base/auto_reset.h"
#include "base/environment.h"
#include "ui/base/ime/ime_bridge.h"
#include "ui/base/ime/ime_engine_handler_interface.h"
#include "ui/base/ime/linux/linux_input_method_context_factory.h"
#include "ui/base/ime/text_input_client.h"
#include "ui/events/event.h"
namespace {
ui::IMEEngineHandlerInterface* GetEngine() {
if (ui::IMEBridge::Get())
return ui::IMEBridge::Get()->GetCurrentEngineHandler();
return nullptr;
}
} // namespace
namespace ui {
InputMethodAuraLinux::InputMethodAuraLinux(
internal::InputMethodDelegate* delegate)
: text_input_type_(TEXT_INPUT_TYPE_NONE),
is_sync_mode_(false),
composition_changed_(false),
suppress_next_result_(false),
weak_ptr_factory_(this) {
SetDelegate(delegate);
context_ =
LinuxInputMethodContextFactory::instance()->CreateInputMethodContext(
this, false);
context_simple_ =
LinuxInputMethodContextFactory::instance()->CreateInputMethodContext(
this, true);
}
InputMethodAuraLinux::~InputMethodAuraLinux() {
}
LinuxInputMethodContext* InputMethodAuraLinux::GetContextForTesting(
bool is_simple) {
return is_simple ? context_simple_.get() : context_.get();
}
// Overriden from InputMethod.
bool InputMethodAuraLinux::OnUntranslatedIMEMessage(
const base::NativeEvent& event,
NativeEventResult* result) {
return false;
}
void InputMethodAuraLinux::DispatchKeyEvent(ui::KeyEvent* event) {
DCHECK(event->type() == ET_KEY_PRESSED || event->type() == ET_KEY_RELEASED);
// If no text input client, do nothing.
if (!GetTextInputClient()) {
ignore_result(DispatchKeyEventPostIME(event));
return;
}
if (!event->HasNativeEvent() && sending_key_event_) {
// Faked key events that are sent from input.ime.sendKeyEvents.
ui::EventDispatchDetails details = DispatchKeyEventPostIME(event);
if (details.dispatcher_destroyed || details.target_destroyed ||
event->stopped_propagation()) {
return;
}
if ((event->is_char() || event->GetDomKey().IsCharacter()) &&
event->type() == ui::ET_KEY_PRESSED) {
GetTextInputClient()->InsertChar(*event);
}
return;
}
suppress_next_result_ = false;
composition_changed_ = false;
result_text_.clear();
bool filtered = false;
{
base::AutoReset<bool> flipper(&is_sync_mode_, true);
if (text_input_type_ != TEXT_INPUT_TYPE_NONE &&
text_input_type_ != TEXT_INPUT_TYPE_PASSWORD) {
filtered = context_->DispatchKeyEvent(*event);
} else {
filtered = context_simple_->DispatchKeyEvent(*event);
}
}
// If there's an active IME extension is listening to the key event, and the
// current text input client is not password input client, the key event
// should be dispatched to the extension engine in the two conditions:
// 1) |filtered| == false: the ET_KEY_PRESSED event of non-character key,
// or the ET_KEY_RELEASED event of all key.
// 2) |filtered| == true && NeedInsertChar(): the ET_KEY_PRESSED event of
// character key.
if (text_input_type_ != TEXT_INPUT_TYPE_PASSWORD &&
GetEngine() && GetEngine()->IsInterestedInKeyEvent() &&
(!filtered || NeedInsertChar())) {
ui::IMEEngineHandlerInterface::KeyEventDoneCallback callback = base::Bind(
&InputMethodAuraLinux::ProcessKeyEventByEngineDone,
weak_ptr_factory_.GetWeakPtr(), base::Owned(new ui::KeyEvent(*event)),
filtered, composition_changed_,
base::Owned(new ui::CompositionText(composition_)),
base::Owned(new base::string16(result_text_)));
GetEngine()->ProcessKeyEvent(*event, callback);
} else {
ProcessKeyEventDone(event, filtered, false);
}
}
void InputMethodAuraLinux::ProcessKeyEventByEngineDone(
ui::KeyEvent* event,
bool filtered,
bool composition_changed,
ui::CompositionText* composition,
base::string16* result_text,
bool is_handled) {
composition_changed_ = composition_changed;
composition_.CopyFrom(*composition);
result_text_ = *result_text;
ProcessKeyEventDone(event, filtered, is_handled);
}
void InputMethodAuraLinux::ProcessKeyEventDone(ui::KeyEvent* event,
bool filtered,
bool is_handled) {
DCHECK(event);
if (is_handled)
return;
// If the IME extension has not handled the key event, passes the keyevent
// back to the previous processing flow. Preconditions for this situation:
// 1) |filtered| == false
// 2) |filtered| == true && NeedInsertChar()
ui::EventDispatchDetails details;
if (event->type() == ui::ET_KEY_PRESSED && filtered) {
if (NeedInsertChar())
details = DispatchKeyEventPostIME(event);
else if (HasInputMethodResult())
details = SendFakeProcessKeyEvent(event);
if (details.dispatcher_destroyed)
return;
// If the KEYDOWN is stopped propagation (e.g. triggered an accelerator),
// don't InsertChar/InsertText to the input field.
if (event->stopped_propagation() || details.target_destroyed) {
ResetContext();
return;
}
// Don't send VKEY_PROCESSKEY event if there is no result text or
// composition. This is to workaround the weird behavior of IBus with US
// keyboard, which mutes the keydown and later fake a new keydown with IME
// result in sync mode. In that case, user would expect only
// keydown/keypress/keyup event without an initial 229 keydown event.
}
bool should_stop_propagation = false;
// Note: |client| could be NULL because DispatchKeyEventPostIME could have
// changed the text input client.
TextInputClient* client = GetTextInputClient();
// Processes the result text before composition for sync mode.
if (client && !result_text_.empty()) {
if (filtered && NeedInsertChar()) {
for (const auto ch : result_text_) {
ui::KeyEvent ch_event(*event);
ch_event.set_character(ch);
client->InsertChar(ch_event);
}
} else {
// If |filtered| is false, that means the IME wants to commit some text
// but still release the key to the application. For example, Korean IME
// handles ENTER key to confirm its composition but still release it for
// the default behavior (e.g. trigger search, etc.)
// In such case, don't do InsertChar because a key should only trigger the
// keydown event once.
client->InsertText(result_text_);
}
should_stop_propagation = true;
}
if (client && composition_changed_ && !IsTextInputTypeNone()) {
// If composition changed, does SetComposition if composition is not empty.
// And ClearComposition if composition is empty.
if (!composition_.text.empty())
client->SetCompositionText(composition_);
else if (result_text_.empty())
client->ClearCompositionText();
should_stop_propagation = true;
}
// Makes sure the cached composition is cleared after committing any text or
// cleared composition.
if (client && !client->HasCompositionText())
composition_.Clear();
if (!filtered) {
details = DispatchKeyEventPostIME(event);
if (details.dispatcher_destroyed) {
if (should_stop_propagation)
event->StopPropagation();
return;
}
if (event->stopped_propagation() || details.target_destroyed) {
ResetContext();
} else if (event->type() == ui::ET_KEY_PRESSED) {
// If a key event was not filtered by |context_| or |context_simple_|,
// then it means the key event didn't generate any result text. For some
// cases, the key event may still generate a valid character, eg. a
// control-key event (ctrl-a, return, tab, etc.). We need to send the
// character to the focused text input client by calling
// TextInputClient::InsertChar().
// Note: don't use |client| and use GetTextInputClient() here because
// DispatchKeyEventPostIME may cause the current text input client change.
base::char16 ch = event->GetCharacter();
if (ch && GetTextInputClient())
GetTextInputClient()->InsertChar(*event);
should_stop_propagation = true;
}
}
if (should_stop_propagation)
event->StopPropagation();
}
void InputMethodAuraLinux::UpdateContextFocusState() {
bool old_text_input_type = text_input_type_;
text_input_type_ = GetTextInputType();
// We only focus in |context_| when the focus is in a textfield.
if (old_text_input_type != TEXT_INPUT_TYPE_NONE &&
text_input_type_ == TEXT_INPUT_TYPE_NONE) {
context_->Blur();
} else if (old_text_input_type == TEXT_INPUT_TYPE_NONE &&
text_input_type_ != TEXT_INPUT_TYPE_NONE) {
context_->Focus();
}
// |context_simple_| can be used in any textfield, including password box, and
// even if the focused text input client's text input type is
// ui::TEXT_INPUT_TYPE_NONE.
if (GetTextInputClient())
context_simple_->Focus();
else
context_simple_->Blur();
if (!ui::IMEBridge::Get()) // IMEBridge could be null for tests.
return;
ui::IMEEngineHandlerInterface::InputContext context(
GetTextInputType(), GetTextInputMode(), GetTextInputFlags());
ui::IMEBridge::Get()->SetCurrentInputContext(context);
ui::IMEEngineHandlerInterface* engine = GetEngine();
if (engine) {
if (old_text_input_type != TEXT_INPUT_TYPE_NONE)
engine->FocusOut();
if (text_input_type_ != TEXT_INPUT_TYPE_NONE)
engine->FocusIn(context);
}
}
void InputMethodAuraLinux::OnTextInputTypeChanged(
const TextInputClient* client) {
UpdateContextFocusState();
InputMethodBase::OnTextInputTypeChanged(client);
// TODO(yoichio): Support inputmode HTML attribute.
}
void InputMethodAuraLinux::OnCaretBoundsChanged(const TextInputClient* client) {
if (!IsTextInputClientFocused(client))
return;
NotifyTextInputCaretBoundsChanged(client);
context_->SetCursorLocation(GetTextInputClient()->GetCaretBounds());
if (!IsTextInputTypeNone() && text_input_type_ != TEXT_INPUT_TYPE_PASSWORD &&
GetEngine())
GetEngine()->SetCompositionBounds(GetCompositionBounds(client));
}
void InputMethodAuraLinux::CancelComposition(const TextInputClient* client) {
if (!IsTextInputClientFocused(client))
return;
if (GetEngine())
GetEngine()->Reset();
ResetContext();
}
void InputMethodAuraLinux::ResetContext() {
if (!GetTextInputClient())
return;
// To prevent any text from being committed when resetting the |context_|;
is_sync_mode_ = true;
suppress_next_result_ = true;
context_->Reset();
context_simple_->Reset();
// Some input methods may not honour the reset call. Focusing out/in the
// |context_| to make sure it gets reset correctly.
if (text_input_type_ != TEXT_INPUT_TYPE_NONE) {
context_->Blur();
context_->Focus();
}
composition_.Clear();
result_text_.clear();
is_sync_mode_ = false;
composition_changed_ = false;
}
void InputMethodAuraLinux::OnInputLocaleChanged() {
}
std::string InputMethodAuraLinux::GetInputLocale() {
return "";
}
bool InputMethodAuraLinux::IsCandidatePopupOpen() const {
// There seems no way to detect candidate windows or any popups.
return false;
}
// Overriden from ui::LinuxInputMethodContextDelegate
void InputMethodAuraLinux::OnCommit(const base::string16& text) {
if (suppress_next_result_ || !GetTextInputClient()) {
suppress_next_result_ = false;
return;
}
if (is_sync_mode_) {
// Append the text to the buffer, because commit signal might be fired
// multiple times when processing a key event.
result_text_.append(text);
} else if (!IsTextInputTypeNone()) {
// If we are not handling key event, do not bother sending text result if
// the focused text input client does not support text input.
ui::KeyEvent event(ui::ET_KEY_PRESSED, ui::VKEY_PROCESSKEY, 0);
ui::EventDispatchDetails details = SendFakeProcessKeyEvent(&event);
if (details.dispatcher_destroyed)
return;
if (!event.stopped_propagation() && !details.target_destroyed)
GetTextInputClient()->InsertText(text);
composition_.Clear();
}
}
void InputMethodAuraLinux::OnPreeditChanged(
const CompositionText& composition_text) {
if (suppress_next_result_ || IsTextInputTypeNone())
return;
if (is_sync_mode_) {
if (!composition_.text.empty() || !composition_text.text.empty())
composition_changed_ = true;
} else {
ui::KeyEvent event(ui::ET_KEY_PRESSED, ui::VKEY_PROCESSKEY, 0);
ui::EventDispatchDetails details = SendFakeProcessKeyEvent(&event);
if (details.dispatcher_destroyed)
return;
if (!event.stopped_propagation() && !details.target_destroyed)
GetTextInputClient()->SetCompositionText(composition_text);
}
composition_ = composition_text;
}
void InputMethodAuraLinux::OnPreeditEnd() {
if (suppress_next_result_ || IsTextInputTypeNone())
return;
if (is_sync_mode_) {
if (!composition_.text.empty()) {
composition_.Clear();
composition_changed_ = true;
}
} else {
TextInputClient* client = GetTextInputClient();
if (client && client->HasCompositionText()) {
ui::KeyEvent event(ui::ET_KEY_PRESSED, ui::VKEY_PROCESSKEY, 0);
ui::EventDispatchDetails details = SendFakeProcessKeyEvent(&event);
if (details.dispatcher_destroyed)
return;
if (!event.stopped_propagation() && !details.target_destroyed)
client->ClearCompositionText();
}
composition_.Clear();
}
}
// Overridden from InputMethodBase.
void InputMethodAuraLinux::OnWillChangeFocusedClient(
TextInputClient* focused_before,
TextInputClient* focused) {
ConfirmCompositionText();
}
void InputMethodAuraLinux::OnDidChangeFocusedClient(
TextInputClient* focused_before,
TextInputClient* focused) {
UpdateContextFocusState();
// Force to update caret bounds, in case the View thinks that the caret
// bounds has not changed.
if (text_input_type_ != TEXT_INPUT_TYPE_NONE)
OnCaretBoundsChanged(GetTextInputClient());
InputMethodBase::OnDidChangeFocusedClient(focused_before, focused);
}
// private
bool InputMethodAuraLinux::HasInputMethodResult() {
return !result_text_.empty() || composition_changed_;
}
bool InputMethodAuraLinux::NeedInsertChar() const {
return IsTextInputTypeNone() ||
(!composition_changed_ && composition_.text.empty() &&
result_text_.length() == 1);
}
ui::EventDispatchDetails InputMethodAuraLinux::SendFakeProcessKeyEvent(
ui::KeyEvent* event) const {
KeyEvent key_event(ui::ET_KEY_PRESSED, ui::VKEY_PROCESSKEY, event->flags());
ui::EventDispatchDetails details = DispatchKeyEventPostIME(&key_event);
if (key_event.stopped_propagation())
event->StopPropagation();
return details;
}
void InputMethodAuraLinux::ConfirmCompositionText() {
TextInputClient* client = GetTextInputClient();
if (client && client->HasCompositionText()) {
client->ConfirmCompositionText();
if (GetEngine())
GetEngine()->Reset();
}
ResetContext();
}
} // namespace ui