blob: 73ae15fe38e7a9134176234d1d3943b9ddfd6217 [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-unstable-v1-server-protocol.h>
#include <wayland-server-core.h>
#include <wayland-server-protocol-core.h>
#include "base/strings/utf_string_conversions.h"
#include "components/exo/text_input.h"
#include "components/exo/wayland/server_util.h"
#include "ui/events/keycodes/dom/keycode_converter.h"
#if defined(OS_CHROMEOS)
#include "ui/events/ozone/layout/xkb/xkb_keyboard_layout_engine.h"
#endif
namespace exo {
namespace wayland {
namespace {
////////////////////////////////////////////////////////////////////////////////
// text_input_v1 interface:
class WaylandTextInputDelegate : public TextInput::Delegate {
public:
WaylandTextInputDelegate(wl_resource* text_input) : text_input_(text_input) {}
~WaylandTextInputDelegate() override = default;
void set_surface(wl_resource* surface) { surface_ = surface; }
private:
wl_client* client() { return wl_resource_get_client(text_input_); }
uint32_t next_serial() {
return wl_display_next_serial(wl_client_get_display(client()));
}
// 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 {
zwp_text_input_v1_send_input_panel_state(text_input_, is_visible);
wl_client_flush(client());
}
void SetCompositionText(const ui::CompositionText& composition) override {
for (const auto& span : composition.ime_text_spans) {
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:
style = ZWP_TEXT_INPUT_V1_PREEDIT_STYLE_INCORRECT;
break;
}
const size_t start =
OffsetFromUTF16Offset(composition.text, span.start_offset);
const size_t end =
OffsetFromUTF16Offset(composition.text, span.end_offset);
zwp_text_input_v1_send_preedit_styling(text_input_, start, end - start,
style);
}
const size_t pos =
OffsetFromUTF16Offset(composition.text, composition.selection.start());
zwp_text_input_v1_send_preedit_cursor(text_input_, pos);
const std::string utf8 = base::UTF16ToUTF8(composition.text);
zwp_text_input_v1_send_preedit_string(text_input_, next_serial(),
utf8.c_str(), utf8.c_str());
wl_client_flush(client());
}
void Commit(const base::string16& text) override {
zwp_text_input_v1_send_commit_string(text_input_, next_serial(),
base::UTF16ToUTF8(text).c_str());
wl_client_flush(client());
}
void SetCursor(const gfx::Range& selection) override {
zwp_text_input_v1_send_cursor_position(text_input_, selection.end(),
selection.start());
}
void DeleteSurroundingText(const gfx::Range& range) override {
zwp_text_input_v1_send_delete_surrounding_text(text_input_, range.start(),
range.length());
}
void SendKey(const ui::KeyEvent& event) override {
uint32_t code = ui::KeycodeConverter::DomCodeToNativeKeycode(event.code());
bool pressed = event.flags() | ui::ET_KEY_PRESSED;
// TODO(mukai): consolidate the definition of this modifiers_mask with other
// similar ones in components/exo/keyboard.cc or arc_ime_service.cc.
constexpr uint32_t modifiers_mask =
ui::EF_SHIFT_DOWN | ui::EF_CONTROL_DOWN | ui::EF_ALT_DOWN |
ui::EF_COMMAND_DOWN | ui::EF_ALTGR_DOWN | ui::EF_MOD3_DOWN;
// 1-bit shifts to adjust the bitpattern for the modifiers; see also
// WaylandTextInputDelegate::SendModifiers().
uint32_t modifiers = (event.flags() & modifiers_mask) >> 1;
zwp_text_input_v1_send_keysym(text_input_,
TimeTicksToMilliseconds(event.time_stamp()),
next_serial(), code,
pressed ? WL_KEYBOARD_KEY_STATE_PRESSED
: WL_KEYBOARD_KEY_STATE_RELEASED,
modifiers);
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_, next_serial(),
wayland_direction);
}
wl_resource* text_input_;
wl_resource* surface_ = nullptr;
DISALLOW_COPY_AND_ASSIGN(WaylandTextInputDelegate);
};
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.
constexpr const char* kModifierNames[] = {
XKB_MOD_NAME_SHIFT,
XKB_MOD_NAME_CTRL,
XKB_MOD_NAME_ALT,
XKB_MOD_NAME_LOGO,
"Mod5",
"Mod3",
};
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)->Resync();
}
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);
text_input->SetSurroundingText(base::UTF8ToUTF16(text),
OffsetFromUTF8Offset(text, cursor),
OffsetFromUTF8Offset(text, 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:
DCHECK(!should_do_learning);
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();
}
const 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) {
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)));
}
const struct zwp_text_input_manager_v1_interface
text_input_manager_implementation = {
text_input_manager_create_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);
}
} // namespace wayland
} // namespace exo