// Copyright 2018 The Chromium Authors
// 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 <sys/mman.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 <string_view>
#include "ash/constants/ash_features.h"
#include "base/files/file_util.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.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/wayland/wl_seat.h"
#include "components/exo/xkb_tracker.h"
#include "net/base/data_url.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/dom_code.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",
};
uint32_t keyCharToKeySym(char16_t keychar) {
// TODO(b/237461655): Lacros fails to handle key presses properly when the
// key character is not present in the keyboard layout.
if ((keychar >= 0x20 && keychar <= 0x7e) ||
(keychar >= 0xa0 && keychar <= 0xff)) {
return keychar;
}
// The spec also requires event.GetCharacter() <= 0x10ffff but this is
// always true due to the type of event.GetCharacter().
if (keychar >= 0x100) {
return keychar + 0x01000000;
}
// keysym 0 is used for unidentified events
return 0;
}
////////////////////////////////////////////////////////////////////////////////
// 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_; }
ui::TextInputClient::FocusReason pending_focus_reason() const {
return pending_focus_reason_;
}
void set_pending_focus_reason(ui::TextInputClient::FocusReason reason) {
pending_focus_reason_ = reason;
}
bool pending_surrounding_text_supported() const {
return pending_surrounding_text_supported_;
}
void set_pending_surrounding_text_supported(bool is_supported) {
pending_surrounding_text_supported_ = is_supported;
}
void SetPendingGrammarFragment(
const std::optional<ui::GrammarFragment>& grammar_fragment) {
pending_grammar_fragment_ = grammar_fragment;
}
std::optional<ui::GrammarFragment> TakeGrammarFragment() {
auto result = pending_grammar_fragment_;
pending_grammar_fragment_.reset();
return result;
}
void SetPendingAutocorrectInfo(const ui::AutocorrectInfo& autocorrect_info) {
pending_autocorrect_info_ = autocorrect_info;
}
std::optional<ui::AutocorrectInfo> TakeAutocorrectInfo() {
auto result = pending_autocorrect_info_;
pending_autocorrect_info_.reset();
return result;
}
void SetSurroundingTextOffsetUtf16(uint32_t offset) {
pending_surrounding_text_offset_utf16_ = offset;
}
std::optional<uint32_t> TakeSurroundingTextOffsetUtf16() {
auto result = pending_surrounding_text_offset_utf16_;
pending_surrounding_text_offset_utf16_.reset();
return result;
}
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 OnVirtualKeyboardOccludedBoundsChanged(
const gfx::Rect& screen_bounds) override {
if (!extended_text_input_)
return;
if (wl_resource_get_version(extended_text_input_) >=
ZCR_EXTENDED_TEXT_INPUT_V1_SET_VIRTUAL_KEYBOARD_OCCLUDED_BOUNDS_SINCE_VERSION) {
zcr_extended_text_input_v1_send_set_virtual_keyboard_occluded_bounds(
extended_text_input_, screen_bounds.x(), screen_bounds.y(),
screen_bounds.width(), screen_bounds.height());
wl_client_flush(client());
}
}
bool SupportsFinalizeVirtualKeyboardChanges() override {
return extended_text_input_ &&
wl_resource_get_version(extended_text_input_) >=
ZCR_EXTENDED_TEXT_INPUT_V1_FINALIZE_VIRTUAL_KEYBOARD_CHANGES_SINCE_VERSION;
}
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(std::u16string_view 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(std::u16string_view 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(std::u16string_view 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/40189286): 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]));
wl_client_flush(client());
}
void SendKey(const ui::KeyEvent& event) override {
uint32_t keysym =
event.code() != ui::DomCode::NONE
? xkb_tracker_->GetKeysym(
ui::KeycodeConverter::DomCodeToNativeKeycode(event.code()))
: 0;
// Some artificial key events (e.g. from virtual keyboard) do not set code,
// so must be handled separately.
// https://www.x.org/releases/X11R7.6/doc/xproto/x11protocol.html#keysym_encoding
// suggests that we can just directly map some parts of unicode.
if (keysym == 0) {
keysym = keyCharToKeySym(event.GetCharacter());
}
if (keysym == 0) {
VLOG(0) << "Unable to find keysym for: " << event.ToString();
}
bool pressed = (event.type() == ui::EventType::kKeyPressed);
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(
std::u16string_view 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 ClearGrammarFragments(std::u16string_view surrounding_text,
const gfx::Range& range) override {
if (!extended_text_input_)
return;
if (wl_resource_get_version(extended_text_input_) >=
ZCR_EXTENDED_TEXT_INPUT_V1_CLEAR_GRAMMAR_FRAGMENTS_SINCE_VERSION) {
std::vector<size_t> offsets = {range.start(), range.end()};
base::UTF16ToUTF8AndAdjustOffsets(surrounding_text, &offsets);
zcr_extended_text_input_v1_send_clear_grammar_fragments(
extended_text_input_, static_cast<uint32_t>(offsets[0]),
static_cast<uint32_t>(offsets[1]));
wl_client_flush(client());
}
}
void AddGrammarFragment(std::u16string_view surrounding_text,
const ui::GrammarFragment& fragment) override {
if (!extended_text_input_)
return;
if (wl_resource_get_version(extended_text_input_) >=
ZCR_EXTENDED_TEXT_INPUT_V1_ADD_GRAMMAR_FRAGMENT_SINCE_VERSION) {
std::vector<size_t> offsets = {fragment.range.start(),
fragment.range.end()};
base::UTF16ToUTF8AndAdjustOffsets(surrounding_text, &offsets);
zcr_extended_text_input_v1_send_add_grammar_fragment(
extended_text_input_, static_cast<uint32_t>(offsets[0]),
static_cast<uint32_t>(offsets[1]), fragment.suggestion.c_str());
wl_client_flush(client());
}
}
void SetAutocorrectRange(std::u16string_view surrounding_text,
const gfx::Range& range) override {
if (!extended_text_input_) {
return;
}
if (wl_resource_get_version(extended_text_input_) <
ZCR_EXTENDED_TEXT_INPUT_V1_SET_AUTOCORRECT_RANGE_SINCE_VERSION) {
return;
}
std::vector<size_t> offsets{range.GetMin(), range.GetMax()};
base::UTF16ToUTF8AndAdjustOffsets(surrounding_text, &offsets);
zcr_extended_text_input_v1_send_set_autocorrect_range(
extended_text_input_, offsets[0], offsets[1]);
wl_client_flush(client());
}
bool HasImageInsertSupport() override {
if (!extended_text_input_) {
return false;
}
return wl_resource_get_version(extended_text_input_) >=
ZCR_EXTENDED_TEXT_INPUT_V1_INSERT_IMAGE_SINCE_VERSION;
}
void InsertImage(const GURL& src) override {
if (!extended_text_input_) {
return;
}
// Due to the limit of wayland protocol, we should check the size of
// payload here.
static constexpr size_t kSizeLimit = 4000;
const size_t src_size = src.spec().size();
if (src_size > kSizeLimit) {
if (wl_resource_get_version(extended_text_input_) <
ZCR_EXTENDED_TEXT_INPUT_V1_INSERT_IMAGE_WITH_LARGE_URL_SINCE_VERSION) {
LOG(ERROR) << "Inserting image with large URL is not supported";
return;
}
std::string mime_type, charset, raw_data;
if (!net::DataURL::Parse(src, &mime_type, &charset, &raw_data)) {
LOG(ERROR) << "Failed to parse data url";
return;
}
base::ScopedFD memfd(memfd_create("inserting_image", MFD_CLOEXEC));
if (!memfd.get()) {
PLOG(ERROR) << "Failed to create memfd";
return;
}
if (!base::WriteFileDescriptor(memfd.get(), raw_data)) {
LOG(ERROR) << "Failed to write into memfd";
return;
}
if (lseek(memfd.get(), 0, SEEK_SET) != 0) {
LOG(ERROR) << "Failed to reset file descriptor";
return;
}
zcr_extended_text_input_v1_send_insert_image_with_large_url(
extended_text_input_, mime_type.c_str(), charset.c_str(), memfd.get(),
raw_data.size());
wl_client_flush(client());
return;
}
if (wl_resource_get_version(extended_text_input_) >=
ZCR_EXTENDED_TEXT_INPUT_V1_INSERT_IMAGE_SINCE_VERSION) {
zcr_extended_text_input_v1_send_insert_image(extended_text_input_,
src.spec().c_str());
wl_client_flush(client());
}
}
void SendPreeditStyle(std::u16string_view 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);
}
}
bool ConfirmComposition(bool keep_selection) override {
if (!extended_text_input_) {
return false;
}
if (wl_resource_get_version(extended_text_input_) <
ZCR_EXTENDED_TEXT_INPUT_V1_CONFIRM_PREEDIT_SINCE_VERSION) {
return false;
}
zcr_extended_text_input_v1_send_confirm_preedit(
extended_text_input_,
keep_selection
? ZCR_EXTENDED_TEXT_INPUT_V1_CONFIRM_PREEDIT_SELECTION_BEHAVIOR_UNCHANGED
: ZCR_EXTENDED_TEXT_INPUT_V1_CONFIRM_PREEDIT_SELECTION_BEHAVIOR_AFTER_PREEDIT);
wl_client_flush(client());
return true;
}
bool SupportsConfirmPreedit() override {
// Note: until this is supported by crostini, crostini won't be able to add
// the new extension api.
return extended_text_input_ &&
wl_resource_get_version(extended_text_input_) >=
ZCR_EXTENDED_TEXT_INPUT_V1_CONFIRM_PREEDIT_SINCE_VERSION;
}
raw_ptr<wl_resource, DanglingUntriaged> text_input_;
raw_ptr<wl_resource, DanglingUntriaged> extended_text_input_ = nullptr;
raw_ptr<wl_resource, DanglingUntriaged> surface_ = nullptr;
// Owned by Seat, which is updated before calling the callbacks of this
// class.
const raw_ptr<const XkbTracker> xkb_tracker_;
// Owned by Server, which always outlives this delegate.
const raw_ptr<SerialTracker> serial_tracker_;
ui::XkbModifierConverter modifier_converter_{
std::vector<std::string>(std::begin(kModifierNames),
std::end(kModifierNames))};
// Pending focus reason.
ui::TextInputClient::FocusReason pending_focus_reason_ =
ui::TextInputClient::FOCUS_REASON_OTHER;
// Pending surrounding text supported flag.
bool pending_surrounding_text_supported_ = true;
std::optional<ui::GrammarFragment> pending_grammar_fragment_;
std::optional<ui::AutocorrectInfo> pending_autocorrect_info_;
std::optional<std::uint32_t> pending_surrounding_text_offset_utf16_;
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 SetSurroundingTextImpl(TextInput* text_input,
WaylandTextInputDelegate* delegate,
std::string_view text,
uint32_t cursor,
uint32_t anchor) {
uint32_t offset_utf16 =
delegate->TakeSurroundingTextOffsetUtf16().value_or(0u);
auto grammar_fragment = delegate->TakeGrammarFragment();
auto autocorrect_info = delegate->TakeAutocorrectInfo();
// TODO(crbug.com/40189286): Selection range should keep cursor/anchor
// relationship.
auto minmax = std::minmax(cursor, anchor);
std::vector<size_t> offsets{minmax.first, minmax.second};
if (grammar_fragment.has_value()) {
offsets.push_back(grammar_fragment->range.start());
offsets.push_back(grammar_fragment->range.end());
}
if (autocorrect_info.has_value()) {
offsets.push_back(autocorrect_info->range.start());
offsets.push_back(autocorrect_info->range.end());
}
std::u16string u16_text = base::UTF8ToUTF16AndAdjustOffsets(text, &offsets);
if (offsets[0] == std::u16string::npos ||
offsets[1] == std::u16string::npos) {
return;
}
if (grammar_fragment.has_value()) {
grammar_fragment->range =
gfx::Range(offsets[2] + offset_utf16, offsets[3] + offset_utf16);
}
// Original implementation did not convert the range. Guard this by the
// feature flag to be reverted to old behavior just in case for transition
// period.
if (autocorrect_info.has_value()) {
size_t index = grammar_fragment.has_value() ? 4u : 2u;
autocorrect_info->range = gfx::Range(offsets[index] + offset_utf16,
offsets[index + 1] + offset_utf16);
}
text_input->SetSurroundingText(
u16_text, offset_utf16,
gfx::Range(offsets[0] + offset_utf16, offsets[1] + offset_utf16),
grammar_fragment, autocorrect_info);
}
void text_input_activate(wl_client* client,
wl_resource* resource,
wl_resource* seat_resource,
wl_resource* surface_resource) {
TextInput* text_input = GetUserDataAs<TextInput>(resource);
Surface* surface = GetUserDataAs<Surface>(surface_resource);
Seat* seat = GetUserDataAs<WaylandSeat>(seat_resource)->seat;
auto* delegate =
static_cast<WaylandTextInputDelegate*>(text_input->delegate());
delegate->set_surface(surface_resource);
auto focus_reason = delegate->pending_focus_reason();
delegate->set_pending_focus_reason(ui::TextInputClient::FOCUS_REASON_OTHER);
text_input->Activate(seat, surface, focus_reason);
// 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);
auto* delegate =
static_cast<WaylandTextInputDelegate*>(text_input->delegate());
delegate->set_pending_focus_reason(ui::TextInputClient::FOCUS_REASON_OTHER);
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);
auto* delegate =
static_cast<WaylandTextInputDelegate*>(text_input->delegate());
SetSurroundingTextImpl(text_input, delegate, text, cursor, anchor);
}
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;
}
auto* delegate =
static_cast<WaylandTextInputDelegate*>(text_input->delegate());
bool surrounding_text_supported =
delegate->pending_surrounding_text_supported();
delegate->set_pending_surrounding_text_supported(/*is_supported = */ true);
text_input->SetTypeModeFlags(type, mode, flags, should_do_learning,
/* can_compose_inline = */ true,
surrounding_text_supported);
}
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,
uint32_t inline_composition_support) {
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;
bool can_compose_inline =
inline_composition_support ==
ZCR_EXTENDED_TEXT_INPUT_V1_INLINE_COMPOSITION_SUPPORT_SUPPORTED;
bool surrounding_text_supported =
delegate->pending_surrounding_text_supported();
delegate->set_pending_surrounding_text_supported(/*is_supported = */ true);
auto* text_input = GetUserDataAs<TextInput>(delegate->resource());
text_input->SetTypeModeFlags(ui_type, ui_mode, ui_flags, should_do_learning,
can_compose_inline, surrounding_text_supported);
}
void extended_text_input_deprecated_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) {
// TODO(crbug.com/40258785) This deprecated method signature is preserved to
// maintain backwards compatibility with older client versions. Once both Exo
// and Lacros have stabilized on the new API, delete this implementation or
// otherwise make it impossible to call.
extended_text_input_set_input_type(client, resource, input_type, input_mode,
input_flags, learning_mode,
/*inline_composition_support=*/true);
}
void extended_text_input_set_grammar_fragment_at_cursor(
wl_client* client,
wl_resource* resource,
uint32_t start,
uint32_t end,
const char* suggestion) {
auto* delegate =
GetUserDataAs<WaylandExtendedTextInput>(resource)->delegate();
if (!delegate) {
return;
}
delegate->SetPendingGrammarFragment(
start == end ? std::nullopt
: std::make_optional(ui::GrammarFragment(
gfx::Range(start, end), suggestion)));
}
void extended_text_input_set_autocorrect_info(wl_client* client,
wl_resource* resource,
uint32_t start,
uint32_t end,
uint32_t x,
uint32_t y,
uint32_t width,
uint32_t height) {
auto* delegate =
GetUserDataAs<WaylandExtendedTextInput>(resource)->delegate();
if (!delegate) {
return;
}
delegate->SetPendingAutocorrectInfo(ui::AutocorrectInfo{
gfx::Range(start, end),
gfx::Rect(x, y, width, height),
});
}
void extended_text_input_finalize_virtual_keyboard_changes(
wl_client* client,
wl_resource* resource) {
auto* delegate =
GetUserDataAs<WaylandExtendedTextInput>(resource)->delegate();
if (!delegate)
return;
auto* text_input = GetUserDataAs<TextInput>(delegate->resource());
text_input->FinalizeVirtualKeyboardChanges();
}
void extended_text_input_set_focus_reason(wl_client* client,
wl_resource* resource,
uint32_t reason) {
ui::TextInputClient::FocusReason focus_reason;
switch (reason) {
case ZCR_EXTENDED_TEXT_INPUT_V1_FOCUS_REASON_TYPE_NONE:
focus_reason = ui::TextInputClient::FOCUS_REASON_NONE;
break;
case ZCR_EXTENDED_TEXT_INPUT_V1_FOCUS_REASON_TYPE_MOUSE:
focus_reason = ui::TextInputClient::FOCUS_REASON_MOUSE;
break;
case ZCR_EXTENDED_TEXT_INPUT_V1_FOCUS_REASON_TYPE_TOUCH:
focus_reason = ui::TextInputClient::FOCUS_REASON_TOUCH;
break;
case ZCR_EXTENDED_TEXT_INPUT_V1_FOCUS_REASON_TYPE_PEN:
focus_reason = ui::TextInputClient::FOCUS_REASON_PEN;
break;
case ZCR_EXTENDED_TEXT_INPUT_V1_FOCUS_REASON_TYPE_OTHER:
focus_reason = ui::TextInputClient::FOCUS_REASON_OTHER;
break;
default:
LOG(ERROR) << "Unknown focus reason: " << reason;
return;
}
// Keep tracking in WaylandExtendedTextInput. This will be passed to
// TextInput::Activate.
auto* delegate =
GetUserDataAs<WaylandExtendedTextInput>(resource)->delegate();
if (!delegate) {
return;
}
delegate->set_pending_focus_reason(focus_reason);
}
void extended_text_input_set_surrounding_text_support(wl_client* client,
wl_resource* resource,
uint32_t support) {
auto* delegate =
GetUserDataAs<WaylandExtendedTextInput>(resource)->delegate();
if (!delegate) {
return;
}
switch (support) {
case ZCR_EXTENDED_TEXT_INPUT_V1_SURROUNDING_TEXT_SUPPORT_SUPPORTED:
delegate->set_pending_surrounding_text_supported(/*is_supported=*/true);
return;
case ZCR_EXTENDED_TEXT_INPUT_V1_SURROUNDING_TEXT_SUPPORT_UNSUPPORTED:
delegate->set_pending_surrounding_text_supported(/*is_supported=*/false);
return;
default:
LOG(ERROR) << "Unknown surrounding_text_support: " << support;
return;
}
}
void extended_text_input_set_surrounding_text_offset_utf16(
wl_client* client,
wl_resource* resource,
uint32_t offset_utf16) {
auto* delegate =
GetUserDataAs<WaylandExtendedTextInput>(resource)->delegate();
if (!delegate) {
return;
}
delegate->SetSurroundingTextOffsetUtf16(offset_utf16);
}
void extended_text_input_set_large_surrounding_text(wl_client* client,
wl_resource* resource,
int32_t raw_fd,
uint32_t size,
uint32_t cursor,
uint32_t anchor) {
std::string text;
{
text.resize(size);
base::ScopedFD fd(raw_fd);
if (!base::ReadFromFD(fd.get(), text)) {
PLOG(ERROR) << "Failed to read file descriptor for surrounding text";
return;
}
}
auto* delegate =
GetUserDataAs<WaylandExtendedTextInput>(resource)->delegate();
if (!delegate) {
return;
}
auto* text_input = GetUserDataAs<TextInput>(delegate->resource());
if (!text_input) {
return;
}
SetSurroundingTextImpl(text_input, delegate, text, cursor, anchor);
}
constexpr struct zcr_extended_text_input_v1_interface
extended_text_input_implementation = {
extended_text_input_destroy,
extended_text_input_deprecated_set_input_type,
extended_text_input_set_grammar_fragment_at_cursor,
extended_text_input_set_autocorrect_info,
extended_text_input_finalize_virtual_keyboard_changes,
extended_text_input_set_focus_reason,
extended_text_input_set_input_type,
extended_text_input_set_surrounding_text_support,
extended_text_input_set_surrounding_text_offset_utf16,
extended_text_input_set_large_surrounding_text,
};
////////////////////////////////////////////////////////////////////////////////
// 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