blob: eff38f789d5c8a830c4f7e0ced979a86e71666ab [file] [log] [blame]
// Copyright (c) 2015 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/aura/mus/input_method_mus.h"
#include <utility>
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/strings/string16.h"
#include "services/ws/public/mojom/constants.mojom.h"
#include "services/ws/public/mojom/ime/ime.mojom.h"
#include "services/ws/public/mojom/window_tree_constants.mojom.h"
#include "ui/aura/mus/input_method_mus_delegate.h"
#include "ui/aura/mus/text_input_client_impl.h"
#include "ui/base/ime/text_edit_commands.h"
#include "ui/base/ime/text_input_client.h"
#include "ui/events/event.h"
#include "ui/platform_window/mojo/ime_type_converters.h"
#include "ui/platform_window/mojo/text_input_state.mojom.h"
using ws::mojom::EventResult;
namespace aura {
namespace {
void CallEventResultCallback(InputMethodMus::EventResultCallback ack_callback,
bool handled) {
// |ack_callback| can be null if the standard form of DispatchKeyEvent() is
// called instead of the version which provides a callback. In mus+ash we
// use the version with callback, but some unittests use the standard form.
if (!ack_callback)
return;
std::move(ack_callback)
.Run(handled ? EventResult::HANDLED : EventResult::UNHANDLED);
}
void OnDispatchKeyEventPostIME(InputMethodMus::EventResultCallback callback,
bool handled,
bool stopped_propagation) {
CallEventResultCallback(std::move(callback), handled);
}
ws::mojom::TextInputClientDataPtr GetTextInputClientData(
const ui::TextInputClient* client) {
auto data = ws::mojom::TextInputClientData::New();
data->has_composition_text = client->HasCompositionText();
gfx::Range text_range;
if (client->GetTextRange(&text_range))
data->text_range = text_range;
base::string16 text;
if (data->text_range.has_value() &&
client->GetTextFromRange(*data->text_range, &text)) {
data->text = std::move(text);
}
gfx::Range composition_text_range;
if (client->GetCompositionTextRange(&composition_text_range))
data->composition_text_range = composition_text_range;
gfx::Range editable_selection_range;
if (client->GetEditableSelectionRange(&editable_selection_range))
data->editable_selection_range = editable_selection_range;
const size_t kFirstCommand =
static_cast<size_t>(ui::TextEditCommand::FIRST_COMMAND);
static_assert(kFirstCommand == 0,
"ui::TextEditCommand is used as index to a vector. "
"FIRST_COMMAND must have a numeric value of 0.");
const size_t kLastCommand =
static_cast<size_t>(ui::TextEditCommand::LAST_COMMAND);
std::vector<bool> edit_command_enabled(kLastCommand);
for (size_t i = kFirstCommand; i < kLastCommand; ++i) {
edit_command_enabled[i] =
client->IsTextEditCommandEnabled(static_cast<ui::TextEditCommand>(i));
}
data->edit_command_enabled = std::move(edit_command_enabled);
return data;
}
} // namespace
////////////////////////////////////////////////////////////////////////////////
// InputMethodMus, public:
InputMethodMus::InputMethodMus(
ui::internal::InputMethodDelegate* delegate,
InputMethodMusDelegate* input_method_mus_delegate)
: input_method_mus_delegate_(input_method_mus_delegate) {
SetDelegate(delegate);
}
InputMethodMus::~InputMethodMus() {
// Mus won't dispatch the next key event until the existing one is acked. We
// may have KeyEvents sent to IME and awaiting the result, we need to ack
// them otherwise mus won't process the next event until it times out.
AckPendingCallbacks();
}
void InputMethodMus::Init(service_manager::Connector* connector) {
if (connector)
connector->BindInterface(ws::mojom::kServiceName, &ime_driver_);
}
ui::EventDispatchDetails InputMethodMus::DispatchKeyEvent(
ui::KeyEvent* event,
EventResultCallback ack_callback) {
DCHECK(event->type() == ui::ET_KEY_PRESSED ||
event->type() == ui::ET_KEY_RELEASED);
// If no text input client or the event is synthesized, dispatch the devent
// directly without forwarding it to the real input method.
if (!GetTextInputClient() || (event->flags() & ui::EF_IS_SYNTHESIZED)) {
return DispatchKeyEventPostIME(
event,
base::BindOnce(&OnDispatchKeyEventPostIME, std::move(ack_callback)));
}
return SendKeyEventToInputMethod(*event, std::move(ack_callback));
}
////////////////////////////////////////////////////////////////////////////////
// InputMethodMus, ui::InputMethod implementation:
void InputMethodMus::OnFocus() {
InputMethodBase::OnFocus();
UpdateTextInputType();
}
void InputMethodMus::OnBlur() {
InputMethodBase::OnBlur();
UpdateTextInputType();
}
ui::EventDispatchDetails InputMethodMus::DispatchKeyEvent(ui::KeyEvent* event) {
ui::EventDispatchDetails dispatch_details =
DispatchKeyEvent(event, EventResultCallback());
// Mark the event as handled so that EventGenerator doesn't attempt to
// deliver event as well.
event->SetHandled();
return dispatch_details;
}
void InputMethodMus::OnTextInputTypeChanged(const ui::TextInputClient* client) {
InputMethodBase::OnTextInputTypeChanged(client);
if (!IsTextInputClientFocused(client))
return;
UpdateTextInputType();
if (!input_method_)
return;
auto text_input_state = ws::mojom::TextInputState::New(
client->GetTextInputType(), client->GetTextInputMode(),
client->GetTextDirection(), client->GetTextInputFlags());
input_method_->OnTextInputStateChanged(std::move(text_input_state));
OnTextInputClientDataChanged(client);
}
void InputMethodMus::OnCaretBoundsChanged(const ui::TextInputClient* client) {
if (!IsTextInputClientFocused(client))
return;
// Sends text input client data (if changed) before caret change because
// InputMethodChromeOS accesses the data in its OnCaretBoundsChanged.
OnTextInputClientDataChanged(client);
if (input_method_)
input_method_->OnCaretBoundsChanged(client->GetCaretBounds());
NotifyTextInputCaretBoundsChanged(client);
}
void InputMethodMus::CancelComposition(const ui::TextInputClient* client) {
if (!IsTextInputClientFocused(client))
return;
if (input_method_)
input_method_->CancelComposition();
}
void InputMethodMus::OnInputLocaleChanged() {
// TODO(moshayedi): crbug.com/637418. Not supported in ChromeOS. Investigate
// whether we want to support this or not.
NOTIMPLEMENTED_LOG_ONCE();
}
bool InputMethodMus::IsCandidatePopupOpen() const {
// TODO(moshayedi): crbug.com/637416. Implement this properly when we have a
// mean for displaying candidate list popup.
NOTIMPLEMENTED_LOG_ONCE();
return false;
}
void InputMethodMus::ShowVirtualKeyboardIfEnabled() {
InputMethodBase::ShowVirtualKeyboardIfEnabled();
if (input_method_)
input_method_->ShowVirtualKeyboardIfEnabled();
}
ui::EventDispatchDetails InputMethodMus::SendKeyEventToInputMethod(
const ui::KeyEvent& event,
EventResultCallback ack_callback) {
if (!input_method_) {
// This code path is hit in tests that don't connect to the server.
DCHECK(!ack_callback);
std::unique_ptr<ui::Event> event_clone = ui::Event::Clone(event);
return DispatchKeyEventPostIME(event_clone->AsKeyEvent(),
base::NullCallback());
}
// IME driver will notify us whether it handled the event or not by calling
// ProcessKeyEventCallback(), in which we will run the |ack_callback| to tell
// the window server if client handled the event or not.
pending_callbacks_.push_back(std::move(ack_callback));
input_method_->ProcessKeyEvent(
ui::Event::Clone(event),
base::BindOnce(&InputMethodMus::ProcessKeyEventCallback,
base::Unretained(this), event));
return ui::EventDispatchDetails();
}
void InputMethodMus::OnDidChangeFocusedClient(
ui::TextInputClient* focused_before,
ui::TextInputClient* focused) {
InputMethodBase::OnDidChangeFocusedClient(focused_before, focused);
UpdateTextInputType();
// We are about to close the pipe with pending callbacks. Closing the pipe
// results in none of the callbacks being run. We have to run the callbacks
// else mus won't process the next event immediately.
AckPendingCallbacks();
if (!focused) {
input_method_ = nullptr;
input_method_ptr_.reset();
text_input_client_.reset();
last_sent_text_input_client_data_.reset();
return;
}
text_input_client_ = std::make_unique<TextInputClientImpl>(
focused, delegate(),
base::BindRepeating(&InputMethodMus::OnTextInputClientDataChanged,
base::Unretained(this)));
if (ime_driver_) {
ws::mojom::SessionDetailsPtr details = ws::mojom::SessionDetails::New();
details->state = ws::mojom::TextInputState::New(
focused->GetTextInputType(), focused->GetTextInputMode(),
focused->GetTextDirection(), focused->GetTextInputFlags());
details->caret_bounds = focused->GetCaretBounds();
details->data = GetTextInputClientData(focused);
last_sent_text_input_client_data_ = details->data->Clone();
details->focus_reason = focused->GetFocusReason();
details->client_source_for_metrics = focused->GetClientSourceForMetrics();
details->should_do_learning = focused->ShouldDoLearning();
ime_driver_->StartSession(MakeRequest(&input_method_ptr_),
text_input_client_->CreateInterfacePtrAndBind(),
std::move(details));
input_method_ = input_method_ptr_.get();
}
}
void InputMethodMus::UpdateTextInputType() {
ui::TextInputType type = GetTextInputType();
ui::mojom::TextInputStatePtr state = ui::mojom::TextInputState::New();
state->type = mojo::ConvertTo<ui::mojom::TextInputType>(type);
if (input_method_mus_delegate_) {
if (type != ui::TEXT_INPUT_TYPE_NONE)
input_method_mus_delegate_->SetImeVisibility(true, std::move(state));
else
input_method_mus_delegate_->SetTextInputState(std::move(state));
}
}
void InputMethodMus::AckPendingCallbacks() {
for (auto& callback : pending_callbacks_) {
if (callback)
std::move(callback).Run(EventResult::HANDLED);
}
pending_callbacks_.clear();
}
void InputMethodMus::ProcessKeyEventCallback(
const ui::KeyEvent& event,
bool handled) {
// Remove the callback as DispatchKeyEventPostIME() may lead to calling
// AckPendingCallbacksUnhandled(), which mutates |pending_callbacks_|.
DCHECK(!pending_callbacks_.empty());
EventResultCallback ack_callback = std::move(pending_callbacks_.front());
pending_callbacks_.pop_front();
CallEventResultCallback(std::move(ack_callback), handled);
}
void InputMethodMus::OnTextInputClientDataChanged(
const ui::TextInputClient* client) {
if (!input_method_ || !IsTextInputClientFocused(client))
return;
auto data = GetTextInputClientData(client);
if (last_sent_text_input_client_data_ == data)
return;
last_sent_text_input_client_data_ = data->Clone();
input_method_->OnTextInputClientDataChanged(std::move(data));
}
} // namespace aura