blob: acd824da85c4028b331ec386c5477c75a814343f [file] [log] [blame]
// Copyright 2018 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 "components/exo/wayland/zwp_text_input_manager.h"
#include <text-input-extension-unstable-v1-server-protocol.h>
#include <text-input-unstable-v1-server-protocol.h>
#include <wayland-server-core.h>
#include <wayland-server-protocol-core.h>
#include <xkbcommon/xkbcommon.h>
#include "base/memory/weak_ptr.h"
#include "base/strings/string_piece.h"
#include "base/strings/utf_offset_string_conversions.h"
#include "base/strings/utf_string_conversions.h"
#include "components/exo/display.h"
#include "components/exo/text_input.h"
#include "components/exo/wayland/serial_tracker.h"
#include "components/exo/wayland/server_util.h"
#include "components/exo/xkb_tracker.h"
#include "ui/base/ime/utf_offset.h"
#include "ui/base/wayland/wayland_server_input_types.h"
#include "ui/events/event.h"
#include "ui/events/keycodes/dom/keycode_converter.h"
#include "ui/events/ozone/layout/xkb/xkb_modifier_converter.h"
namespace exo {
namespace wayland {
namespace {
// The list of modifiers that this supports.
// This is consistent with X.h.
constexpr const char* kModifierNames[] = {
XKB_MOD_NAME_SHIFT, XKB_MOD_NAME_CAPS,
XKB_MOD_NAME_CTRL, XKB_MOD_NAME_ALT,
XKB_MOD_NAME_NUM, "Mod3",
XKB_MOD_NAME_LOGO, "Mod5",
};
////////////////////////////////////////////////////////////////////////////////
// text_input_v1 interface:
class WaylandTextInputDelegate : public TextInput::Delegate {
public:
WaylandTextInputDelegate(wl_resource* text_input,
const XkbTracker* xkb_tracker,
SerialTracker* serial_tracker)
: text_input_(text_input),
xkb_tracker_(xkb_tracker),
serial_tracker_(serial_tracker) {}
~WaylandTextInputDelegate() override = default;
void set_surface(wl_resource* surface) { surface_ = surface; }
void set_extended_text_input(wl_resource* extended_text_input) {
extended_text_input_ = extended_text_input;
}
bool has_extended_text_input() const { return extended_text_input_; }
base::WeakPtr<WaylandTextInputDelegate> GetWeakPtr() {
return weak_factory_.GetWeakPtr();
}
wl_resource* resource() { return text_input_; }
private:
wl_client* client() { return wl_resource_get_client(text_input_); }
// TextInput::Delegate:
void Activated() override {
zwp_text_input_v1_send_enter(text_input_, surface_);
wl_client_flush(client());
}
void Deactivated() override {
zwp_text_input_v1_send_leave(text_input_);
wl_client_flush(client());
}
void OnVirtualKeyboardVisibilityChanged(bool is_visible) override {
// The detailed spec of |state| is implementation dependent.
// So, now we use the lowest bit to indicate whether keyboard is visible.
// This behavior is consistent with ozone/wayland to support Lacros.
zwp_text_input_v1_send_input_panel_state(text_input_,
static_cast<uint32_t>(is_visible));
wl_client_flush(client());
}
void SetCompositionText(const ui::CompositionText& composition) override {
SendPreeditStyle(composition.text, composition.ime_text_spans);
std::vector<size_t> offsets = {composition.selection.start()};
const std::string utf8 =
base::UTF16ToUTF8AndAdjustOffsets(composition.text, &offsets);
if (offsets[0] != std::string::npos)
zwp_text_input_v1_send_preedit_cursor(text_input_, offsets[0]);
zwp_text_input_v1_send_preedit_string(
text_input_,
serial_tracker_->GetNextSerial(SerialTracker::EventType::OTHER_EVENT),
utf8.c_str(), utf8.c_str());
wl_client_flush(client());
}
void Commit(const std::u16string& text) override {
zwp_text_input_v1_send_commit_string(
text_input_,
serial_tracker_->GetNextSerial(SerialTracker::EventType::OTHER_EVENT),
base::UTF16ToUTF8(text).c_str());
wl_client_flush(client());
}
void SetCursor(base::StringPiece16 surrounding_text,
const gfx::Range& selection) override {
std::vector<size_t> offsets{selection.start(), selection.end()};
base::UTF16ToUTF8AndAdjustOffsets(surrounding_text, &offsets);
zwp_text_input_v1_send_cursor_position(text_input_,
static_cast<uint32_t>(offsets[1]),
static_cast<uint32_t>(offsets[0]));
}
void DeleteSurroundingText(base::StringPiece16 surrounding_text,
const gfx::Range& range) override {
std::vector<size_t> offsets{range.GetMin(), range.GetMax()};
base::UTF16ToUTF8AndAdjustOffsets(surrounding_text, &offsets);
// Currently, the arguments are conflicting with spec.
// However, the only client, Lacros, also interprets wrongly in the same
// way so just fixing here could cause visible regression.
// TODO(crbug.com/1227590): Fix the behavior with versioning.
zwp_text_input_v1_send_delete_surrounding_text(
text_input_, static_cast<uint32_t>(offsets[0]),
static_cast<uint32_t>(offsets[1] - offsets[0]));
}
void SendKey(const ui::KeyEvent& event) override {
uint32_t keysym = xkb_tracker_->GetKeysym(
ui::KeycodeConverter::DomCodeToNativeKeycode(event.code()));
bool pressed = (event.type() == ui::ET_KEY_PRESSED);
zwp_text_input_v1_send_keysym(
text_input_, TimeTicksToMilliseconds(event.time_stamp()),
serial_tracker_->GetNextSerial(SerialTracker::EventType::OTHER_EVENT),
keysym,
pressed ? WL_KEYBOARD_KEY_STATE_PRESSED
: WL_KEYBOARD_KEY_STATE_RELEASED,
modifier_converter_.MaskFromUiFlags(event.flags()));
wl_client_flush(client());
}
void OnTextDirectionChanged(base::i18n::TextDirection direction) override {
uint32_t wayland_direction = ZWP_TEXT_INPUT_V1_TEXT_DIRECTION_AUTO;
switch (direction) {
case base::i18n::RIGHT_TO_LEFT:
wayland_direction = ZWP_TEXT_INPUT_V1_TEXT_DIRECTION_LTR;
break;
case base::i18n::LEFT_TO_RIGHT:
wayland_direction = ZWP_TEXT_INPUT_V1_TEXT_DIRECTION_RTL;
break;
case base::i18n::UNKNOWN_DIRECTION:
LOG(ERROR) << "Unrecognized direction: " << direction;
}
zwp_text_input_v1_send_text_direction(
text_input_,
serial_tracker_->GetNextSerial(SerialTracker::EventType::OTHER_EVENT),
wayland_direction);
}
void SetCompositionFromExistingText(
base::StringPiece16 surrounding_text,
const gfx::Range& cursor,
const gfx::Range& range,
const std::vector<ui::ImeTextSpan>& ui_ime_text_spans) override {
if (!extended_text_input_)
return;
uint32_t begin = range.GetMin();
uint32_t end = range.GetMax();
SendPreeditStyle(surrounding_text.substr(begin, range.length()),
ui_ime_text_spans);
std::vector<size_t> offsets = {begin, end, cursor.end()};
base::UTF16ToUTF8AndAdjustOffsets(surrounding_text, &offsets);
int32_t index =
static_cast<int32_t>(offsets[0]) - static_cast<int32_t>(offsets[2]);
uint32_t length = static_cast<uint32_t>(offsets[1] - offsets[0]);
zcr_extended_text_input_v1_send_set_preedit_region(extended_text_input_,
index, length);
wl_client_flush(client());
}
void SendPreeditStyle(base::StringPiece16 text,
const std::vector<ui::ImeTextSpan>& spans) {
if (spans.empty())
return;
// Convert all offsets from UTF16 to UTF8.
std::vector<size_t> offsets;
offsets.reserve(spans.size() * 2);
for (const auto& span : spans) {
auto minmax = std::minmax(span.start_offset, span.end_offset);
offsets.push_back(minmax.first);
offsets.push_back(minmax.second);
}
base::UTF16ToUTF8AndAdjustOffsets(text, &offsets);
for (size_t i = 0; i < spans.size(); ++i) {
if (offsets[i * 2] == std::string::npos ||
offsets[i * 2 + 1] == std::string::npos) {
// Invalid span is specified.
continue;
}
const auto& span = spans[i];
const uint32_t begin = offsets[i * 2];
const uint32_t end = offsets[i * 2 + 1];
uint32_t style = ZWP_TEXT_INPUT_V1_PREEDIT_STYLE_DEFAULT;
switch (span.type) {
case ui::ImeTextSpan::Type::kComposition:
if (span.thickness == ui::ImeTextSpan::Thickness::kThick) {
style = ZWP_TEXT_INPUT_V1_PREEDIT_STYLE_HIGHLIGHT;
} else {
style = ZWP_TEXT_INPUT_V1_PREEDIT_STYLE_UNDERLINE;
}
break;
case ui::ImeTextSpan::Type::kSuggestion:
style = ZWP_TEXT_INPUT_V1_PREEDIT_STYLE_SELECTION;
break;
case ui::ImeTextSpan::Type::kMisspellingSuggestion:
case ui::ImeTextSpan::Type::kAutocorrect:
case ui::ImeTextSpan::Type::kGrammarSuggestion:
style = ZWP_TEXT_INPUT_V1_PREEDIT_STYLE_INCORRECT;
break;
}
zwp_text_input_v1_send_preedit_styling(text_input_, begin, end - begin,
style);
}
}
wl_resource* text_input_;
wl_resource* extended_text_input_ = nullptr;
wl_resource* surface_ = nullptr;
// Owned by Seat, which is updated before calling the callbacks of this
// class.
const XkbTracker* const xkb_tracker_;
// Owned by Server, which always outlives this delegate.
SerialTracker* const serial_tracker_;
ui::XkbModifierConverter modifier_converter_{
std::vector<std::string>(std::begin(kModifierNames),
std::end(kModifierNames))};
base::WeakPtrFactory<WaylandTextInputDelegate> weak_factory_{this};
};
// Holds WeakPtr to WaylandTextInputDelegate, and the lifetime of this class's
// instance is tied to zcr_extended_text_input connection.
// If text_input connection is destroyed earlier than extended_text_input,
// then delegate_ will return nullptr automatically.
class WaylandExtendedTextInput {
public:
explicit WaylandExtendedTextInput(
base::WeakPtr<WaylandTextInputDelegate> delegate)
: delegate_(delegate) {}
WaylandExtendedTextInput(const WaylandExtendedTextInput&) = delete;
WaylandExtendedTextInput& operator=(const WaylandExtendedTextInput&) = delete;
~WaylandExtendedTextInput() {
if (delegate_)
delegate_->set_extended_text_input(nullptr);
}
WaylandTextInputDelegate* delegate() { return delegate_.get(); }
private:
base::WeakPtr<WaylandTextInputDelegate> delegate_;
};
void text_input_activate(wl_client* client,
wl_resource* resource,
wl_resource* seat,
wl_resource* surface_resource) {
TextInput* text_input = GetUserDataAs<TextInput>(resource);
Surface* surface = GetUserDataAs<Surface>(surface_resource);
static_cast<WaylandTextInputDelegate*>(text_input->delegate())
->set_surface(surface_resource);
text_input->Activate(surface);
// Sending modifiers.
wl_array modifiers;
wl_array_init(&modifiers);
for (const char* modifier : kModifierNames) {
char* p =
static_cast<char*>(wl_array_add(&modifiers, ::strlen(modifier) + 1));
::strcpy(p, modifier);
}
zwp_text_input_v1_send_modifiers_map(resource, &modifiers);
wl_array_release(&modifiers);
}
void text_input_deactivate(wl_client* client,
wl_resource* resource,
wl_resource* seat) {
TextInput* text_input = GetUserDataAs<TextInput>(resource);
text_input->Deactivate();
}
void text_input_show_input_panel(wl_client* client, wl_resource* resource) {
GetUserDataAs<TextInput>(resource)->ShowVirtualKeyboardIfEnabled();
}
void text_input_hide_input_panel(wl_client* client, wl_resource* resource) {
GetUserDataAs<TextInput>(resource)->HideVirtualKeyboard();
}
void text_input_reset(wl_client* client, wl_resource* resource) {
GetUserDataAs<TextInput>(resource)->Reset();
}
void text_input_set_surrounding_text(wl_client* client,
wl_resource* resource,
const char* text,
uint32_t cursor,
uint32_t anchor) {
TextInput* text_input = GetUserDataAs<TextInput>(resource);
// TODO(crbug.com/1227590): Selection range should keep cursor/anchor
// relationship.
auto minmax = std::minmax(cursor, anchor);
std::vector<size_t> offsets{minmax.first, minmax.second};
std::u16string u16_text = base::UTF8ToUTF16AndAdjustOffsets(text, &offsets);
if (offsets[0] == std::u16string::npos || offsets[1] == std::u16string::npos)
return;
text_input->SetSurroundingText(u16_text, gfx::Range(offsets[0], offsets[1]));
}
void text_input_set_content_type(wl_client* client,
wl_resource* resource,
uint32_t hint,
uint32_t purpose) {
TextInput* text_input = GetUserDataAs<TextInput>(resource);
ui::TextInputType type = ui::TEXT_INPUT_TYPE_TEXT;
ui::TextInputMode mode = ui::TEXT_INPUT_MODE_DEFAULT;
int flags = ui::TEXT_INPUT_FLAG_NONE;
bool should_do_learning = true;
if (hint & ZWP_TEXT_INPUT_V1_CONTENT_HINT_AUTO_COMPLETION)
flags |= ui::TEXT_INPUT_FLAG_AUTOCOMPLETE_ON;
if (hint & ZWP_TEXT_INPUT_V1_CONTENT_HINT_AUTO_CORRECTION)
flags |= ui::TEXT_INPUT_FLAG_AUTOCORRECT_ON;
if (hint & ZWP_TEXT_INPUT_V1_CONTENT_HINT_AUTO_CAPITALIZATION)
flags |= ui::TEXT_INPUT_FLAG_AUTOCAPITALIZE_SENTENCES;
if (hint & ZWP_TEXT_INPUT_V1_CONTENT_HINT_LOWERCASE)
flags |= ui::TEXT_INPUT_FLAG_AUTOCAPITALIZE_NONE;
if (hint & ZWP_TEXT_INPUT_V1_CONTENT_HINT_UPPERCASE)
flags |= ui::TEXT_INPUT_FLAG_AUTOCAPITALIZE_CHARACTERS;
if (hint & ZWP_TEXT_INPUT_V1_CONTENT_HINT_TITLECASE)
flags |= ui::TEXT_INPUT_FLAG_AUTOCAPITALIZE_WORDS;
if (hint & ZWP_TEXT_INPUT_V1_CONTENT_HINT_HIDDEN_TEXT) {
flags |= ui::TEXT_INPUT_FLAG_AUTOCOMPLETE_OFF |
ui::TEXT_INPUT_FLAG_AUTOCORRECT_OFF;
}
if (hint & ZWP_TEXT_INPUT_V1_CONTENT_HINT_SENSITIVE_DATA)
should_do_learning = false;
// Unused hints: LATIN, MULTILINE.
switch (purpose) {
case ZWP_TEXT_INPUT_V1_CONTENT_PURPOSE_DIGITS:
mode = ui::TEXT_INPUT_MODE_DECIMAL;
type = ui::TEXT_INPUT_TYPE_NUMBER;
break;
case ZWP_TEXT_INPUT_V1_CONTENT_PURPOSE_NUMBER:
mode = ui::TEXT_INPUT_MODE_NUMERIC;
type = ui::TEXT_INPUT_TYPE_NUMBER;
break;
case ZWP_TEXT_INPUT_V1_CONTENT_PURPOSE_PHONE:
mode = ui::TEXT_INPUT_MODE_TEL;
type = ui::TEXT_INPUT_TYPE_TELEPHONE;
break;
case ZWP_TEXT_INPUT_V1_CONTENT_PURPOSE_URL:
mode = ui::TEXT_INPUT_MODE_URL;
type = ui::TEXT_INPUT_TYPE_URL;
break;
case ZWP_TEXT_INPUT_V1_CONTENT_PURPOSE_EMAIL:
mode = ui::TEXT_INPUT_MODE_EMAIL;
type = ui::TEXT_INPUT_TYPE_EMAIL;
break;
case ZWP_TEXT_INPUT_V1_CONTENT_PURPOSE_PASSWORD:
should_do_learning = false;
type = ui::TEXT_INPUT_TYPE_PASSWORD;
break;
case ZWP_TEXT_INPUT_V1_CONTENT_PURPOSE_DATE:
type = ui::TEXT_INPUT_TYPE_DATE;
break;
case ZWP_TEXT_INPUT_V1_CONTENT_PURPOSE_TIME:
type = ui::TEXT_INPUT_TYPE_TIME;
break;
case ZWP_TEXT_INPUT_V1_CONTENT_PURPOSE_DATETIME:
type = ui::TEXT_INPUT_TYPE_DATE_TIME;
break;
case ZWP_TEXT_INPUT_V1_CONTENT_PURPOSE_NORMAL:
case ZWP_TEXT_INPUT_V1_CONTENT_PURPOSE_ALPHA:
case ZWP_TEXT_INPUT_V1_CONTENT_PURPOSE_NAME:
case ZWP_TEXT_INPUT_V1_CONTENT_PURPOSE_TERMINAL:
// No special type / mode are set.
break;
}
text_input->SetTypeModeFlags(type, mode, flags, should_do_learning);
}
void text_input_set_cursor_rectangle(wl_client* client,
wl_resource* resource,
int32_t x,
int32_t y,
int32_t width,
int32_t height) {
GetUserDataAs<TextInput>(resource)->SetCaretBounds(
gfx::Rect(x, y, width, height));
}
void text_input_set_preferred_language(wl_client* client,
wl_resource* resource,
const char* language) {
// Nothing needs to be done.
}
void text_input_commit_state(wl_client* client,
wl_resource* resource,
uint32_t serial) {
// Nothing needs to be done.
}
void text_input_invoke_action(wl_client* client,
wl_resource* resource,
uint32_t button,
uint32_t index) {
GetUserDataAs<TextInput>(resource)->Resync();
}
constexpr struct zwp_text_input_v1_interface text_input_v1_implementation = {
text_input_activate,
text_input_deactivate,
text_input_show_input_panel,
text_input_hide_input_panel,
text_input_reset,
text_input_set_surrounding_text,
text_input_set_content_type,
text_input_set_cursor_rectangle,
text_input_set_preferred_language,
text_input_commit_state,
text_input_invoke_action,
};
////////////////////////////////////////////////////////////////////////////////
// text_input_manager_v1 interface:
void text_input_manager_create_text_input(wl_client* client,
wl_resource* resource,
uint32_t id) {
auto* data = GetUserDataAs<WaylandTextInputManager>(resource);
wl_resource* text_input_resource =
wl_resource_create(client, &zwp_text_input_v1_interface, 1, id);
SetImplementation(
text_input_resource, &text_input_v1_implementation,
std::make_unique<TextInput>(std::make_unique<WaylandTextInputDelegate>(
text_input_resource, data->xkb_tracker, data->serial_tracker)));
}
constexpr struct zwp_text_input_manager_v1_interface
text_input_manager_implementation = {
text_input_manager_create_text_input,
};
////////////////////////////////////////////////////////////////////////////////
// extended_text_input_v1 interface:
void extended_text_input_destroy(wl_client* client, wl_resource* resource) {
wl_resource_destroy(resource);
}
void extended_text_input_set_input_type(wl_client* client,
wl_resource* resource,
uint32_t input_type,
uint32_t input_mode,
uint32_t input_flags,
uint32_t learning_mode) {
auto* delegate =
GetUserDataAs<WaylandExtendedTextInput>(resource)->delegate();
if (!delegate)
return;
// If unknown value is passed, fall back to the default one.
auto ui_type =
ui::wayland::ConvertToTextInputType(
static_cast<zcr_extended_text_input_v1_input_type>(input_type))
.value_or(ui::TEXT_INPUT_TYPE_TEXT);
auto ui_mode =
ui::wayland::ConvertToTextInputMode(
static_cast<zcr_extended_text_input_v1_input_mode>(input_mode))
.value_or(ui::TEXT_INPUT_MODE_DEFAULT);
// Ignore unknown flags.
auto ui_flags = ui::wayland::ConvertToTextInputFlags(input_flags).first;
bool should_do_learning =
learning_mode == ZCR_EXTENDED_TEXT_INPUT_V1_LEARNING_MODE_ENABLED;
auto* text_input = GetUserDataAs<TextInput>(delegate->resource());
text_input->SetTypeModeFlags(ui_type, ui_mode, ui_flags, should_do_learning);
}
constexpr struct zcr_extended_text_input_v1_interface
extended_text_input_implementation = {
extended_text_input_destroy,
extended_text_input_set_input_type,
};
////////////////////////////////////////////////////////////////////////////////
// text_input_extension_v1 interface:
void text_input_extension_get_extended_text_input(
wl_client* client,
wl_resource* resource,
uint32_t id,
wl_resource* text_input_resource) {
TextInput* text_input = GetUserDataAs<TextInput>(text_input_resource);
auto* delegate =
static_cast<WaylandTextInputDelegate*>(text_input->delegate());
if (delegate->has_extended_text_input()) {
wl_resource_post_error(
resource, ZCR_TEXT_INPUT_EXTENSION_V1_ERROR_EXTENDED_TEXT_INPUT_EXISTS,
"text_input has already been associated with a extended_text_input "
"object");
return;
}
uint32_t version = wl_resource_get_version(resource);
wl_resource* extended_text_input_resource = wl_resource_create(
client, &zcr_extended_text_input_v1_interface, version, id);
delegate->set_extended_text_input(extended_text_input_resource);
SetImplementation(
extended_text_input_resource, &extended_text_input_implementation,
std::make_unique<WaylandExtendedTextInput>(delegate->GetWeakPtr()));
}
constexpr struct zcr_text_input_extension_v1_interface
text_input_extension_implementation = {
text_input_extension_get_extended_text_input};
} // namespace
void bind_text_input_manager(wl_client* client,
void* data,
uint32_t version,
uint32_t id) {
wl_resource* resource =
wl_resource_create(client, &zwp_text_input_manager_v1_interface, 1, id);
wl_resource_set_implementation(resource, &text_input_manager_implementation,
data, nullptr);
}
void bind_text_input_extension(wl_client* client,
void* data,
uint32_t version,
uint32_t id) {
wl_resource* resource = wl_resource_create(
client, &zcr_text_input_extension_v1_interface, version, id);
wl_resource_set_implementation(resource, &text_input_extension_implementation,
data, nullptr);
}
} // namespace wayland
} // namespace exo