blob: bfd2555a82890519969329749dc63638ee2cd30a [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/memory/ptr_util.h"
#include "services/ui/public/interfaces/constants.mojom.h"
#include "services/ui/public/interfaces/ime/ime.mojom.h"
#include "services/ui/public/interfaces/window_tree_constants.mojom.h"
#include "ui/aura/mus/text_input_client_impl.h"
#include "ui/aura/mus/window_port_mus.h"
#include "ui/aura/window.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 ui::mojom::EventResult;
namespace aura {
////////////////////////////////////////////////////////////////////////////////
// InputMethodMus, public:
InputMethodMus::InputMethodMus(ui::internal::InputMethodDelegate* delegate,
Window* window)
: window_(window) {
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.
AckPendingCallbacksUnhandled();
}
void InputMethodMus::Init(service_manager::Connector* connector) {
if (connector)
connector->BindInterface(ui::mojom::kServiceName, &ime_driver_);
}
ui::EventDispatchDetails InputMethodMus::DispatchKeyEvent(
ui::KeyEvent* event,
std::unique_ptr<EventResultCallback> ack_callback) {
DCHECK(event->type() == ui::ET_KEY_PRESSED ||
event->type() == ui::ET_KEY_RELEASED);
// If no text input client, do nothing.
if (!GetTextInputClient()) {
ui::EventDispatchDetails dispatch_details = DispatchKeyEventPostIME(event);
if (ack_callback) {
ack_callback->Run(event->handled() ? EventResult::HANDLED
: EventResult::UNHANDLED);
}
return dispatch_details;
}
return SendKeyEventToInputMethod(*event, std::move(ack_callback));
}
////////////////////////////////////////////////////////////////////////////////
// InputMethodMus, ui::InputMethod implementation:
void InputMethodMus::OnFocus() {
InputMethodBase::OnFocus();
UpdateTextInputType();
}
void InputMethodMus::OnBlur() {
InputMethodBase::OnBlur();
UpdateTextInputType();
}
bool InputMethodMus::OnUntranslatedIMEMessage(const base::NativeEvent& event,
NativeEventResult* result) {
// This method is not called on non-Windows platforms. See the comments for
// ui::InputMethod::OnUntranslatedIMEMessage().
return false;
}
ui::EventDispatchDetails InputMethodMus::DispatchKeyEvent(ui::KeyEvent* event) {
ui::EventDispatchDetails dispatch_details = DispatchKeyEvent(event, nullptr);
// 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_)
input_method_->OnTextInputTypeChanged(client->GetTextInputType());
}
void InputMethodMus::OnCaretBoundsChanged(const ui::TextInputClient* client) {
if (!IsTextInputClientFocused(client))
return;
if (input_method_)
input_method_->OnCaretBoundsChanged(client->GetCaretBounds());
}
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.
}
bool InputMethodMus::IsCandidatePopupOpen() const {
// TODO(moshayedi): crbug.com/637416. Implement this properly when we have a
// mean for displaying candidate list popup.
return false;
}
ui::EventDispatchDetails InputMethodMus::SendKeyEventToInputMethod(
const ui::KeyEvent& event,
std::unique_ptr<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());
}
// 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::Bind(&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();
// TODO(moshayedi): crbug.com/681563. Handle when there is no focused clients.
if (!focused)
return;
text_input_client_ =
std::make_unique<TextInputClientImpl>(focused, delegate());
// 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.
AckPendingCallbacksUnhandled();
if (ime_driver_) {
ui::mojom::StartSessionDetailsPtr details =
ui::mojom::StartSessionDetails::New();
details->client =
text_input_client_->CreateInterfacePtrAndBind().PassInterface();
details->input_method_request = MakeRequest(&input_method_ptr_);
input_method_ = input_method_ptr_.get();
details->text_input_type = focused->GetTextInputType();
details->text_input_mode = focused->GetTextInputMode();
details->text_direction = focused->GetTextDirection();
details->text_input_flags = focused->GetTextInputFlags();
details->caret_bounds = focused->GetCaretBounds();
ime_driver_->StartSession(std::move(details));
}
}
void InputMethodMus::UpdateTextInputType() {
ui::TextInputType type = GetTextInputType();
ui::mojom::TextInputStatePtr state = ui::mojom::TextInputState::New();
state->type = mojo::ConvertTo<ui::mojom::TextInputType>(type);
if (window_) {
WindowPortMus* window_impl_mus = WindowPortMus::Get(window_);
if (type != ui::TEXT_INPUT_TYPE_NONE)
window_impl_mus->SetImeVisibility(true, std::move(state));
else
window_impl_mus->SetTextInputState(std::move(state));
}
}
void InputMethodMus::AckPendingCallbacksUnhandled() {
for (auto& callback_ptr : pending_callbacks_) {
if (callback_ptr)
callback_ptr->Run(EventResult::UNHANDLED);
}
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());
std::unique_ptr<EventResultCallback> ack_callback =
std::move(pending_callbacks_.front());
pending_callbacks_.pop_front();
// |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)
ack_callback->Run(handled ? EventResult::HANDLED : EventResult::UNHANDLED);
}
} // namespace aura