1 // Copyright 2018 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "components/exo/wayland/zwp_text_input_manager.h"
6
7 #include <text-input-unstable-v1-server-protocol.h>
8 #include <wayland-server-core.h>
9 #include <wayland-server-protocol-core.h>
10 #include <xkbcommon/xkbcommon.h>
11
12 #include "base/strings/string_piece.h"
13 #include "base/strings/utf_string_conversions.h"
14 #include "components/exo/display.h"
15 #include "components/exo/text_input.h"
16 #include "components/exo/wayland/serial_tracker.h"
17 #include "components/exo/wayland/server_util.h"
18 #include "components/exo/xkb_tracker.h"
19 #include "ui/base/ime/utf_offset.h"
20 #include "ui/events/event.h"
21 #include "ui/events/keycodes/dom/keycode_converter.h"
22
23
24 namespace exo {
25 namespace wayland {
26
27 namespace {
28
29 ////////////////////////////////////////////////////////////////////////////////
30 // text_input_v1 interface:
31
32 class WaylandTextInputDelegate : public TextInput::Delegate {
33 public:
WaylandTextInputDelegate(wl_resource * text_input,const XkbTracker * xkb_tracker,SerialTracker * serial_tracker)34 WaylandTextInputDelegate(wl_resource* text_input,
35 const XkbTracker* xkb_tracker,
36 SerialTracker* serial_tracker)
37 : text_input_(text_input),
38 xkb_tracker_(xkb_tracker),
39 serial_tracker_(serial_tracker) {}
40 ~WaylandTextInputDelegate() override = default;
41
set_surface(wl_resource * surface)42 void set_surface(wl_resource* surface) { surface_ = surface; }
43
44 private:
client()45 wl_client* client() { return wl_resource_get_client(text_input_); }
46
47 // TextInput::Delegate:
Activated()48 void Activated() override {
49 zwp_text_input_v1_send_enter(text_input_, surface_);
50 wl_client_flush(client());
51 }
52
Deactivated()53 void Deactivated() override {
54 zwp_text_input_v1_send_leave(text_input_);
55 wl_client_flush(client());
56 }
57
OnVirtualKeyboardVisibilityChanged(bool is_visible)58 void OnVirtualKeyboardVisibilityChanged(bool is_visible) override {
59 zwp_text_input_v1_send_input_panel_state(text_input_, is_visible);
60 wl_client_flush(client());
61 }
62
SetCompositionText(const ui::CompositionText & composition)63 void SetCompositionText(const ui::CompositionText& composition) override {
64 for (const auto& span : composition.ime_text_spans) {
65 uint32_t style = ZWP_TEXT_INPUT_V1_PREEDIT_STYLE_DEFAULT;
66 switch (span.type) {
67 case ui::ImeTextSpan::Type::kComposition:
68 if (span.thickness == ui::ImeTextSpan::Thickness::kThick) {
69 style = ZWP_TEXT_INPUT_V1_PREEDIT_STYLE_HIGHLIGHT;
70 } else {
71 style = ZWP_TEXT_INPUT_V1_PREEDIT_STYLE_UNDERLINE;
72 }
73 break;
74 case ui::ImeTextSpan::Type::kSuggestion:
75 style = ZWP_TEXT_INPUT_V1_PREEDIT_STYLE_SELECTION;
76 break;
77 case ui::ImeTextSpan::Type::kMisspellingSuggestion:
78 case ui::ImeTextSpan::Type::kAutocorrect:
79 style = ZWP_TEXT_INPUT_V1_PREEDIT_STYLE_INCORRECT;
80 break;
81 }
82 const auto start =
83 ui::Utf8OffsetFromUtf16Offset(composition.text, span.start_offset);
84 if (!start)
85 continue;
86 const auto end =
87 ui::Utf8OffsetFromUtf16Offset(composition.text, span.end_offset);
88 if (!end)
89 continue;
90 zwp_text_input_v1_send_preedit_styling(text_input_, *start, *end - *start,
91 style);
92 }
93
94 const auto pos = ui::Utf8OffsetFromUtf16Offset(
95 composition.text, composition.selection.start());
96 if (pos)
97 zwp_text_input_v1_send_preedit_cursor(text_input_, *pos);
98
99 const std::string utf8 = base::UTF16ToUTF8(composition.text);
100 zwp_text_input_v1_send_preedit_string(
101 text_input_,
102 serial_tracker_->GetNextSerial(SerialTracker::EventType::OTHER_EVENT),
103 utf8.c_str(), utf8.c_str());
104
105 wl_client_flush(client());
106 }
107
Commit(const base::string16 & text)108 void Commit(const base::string16& text) override {
109 zwp_text_input_v1_send_commit_string(
110 text_input_,
111 serial_tracker_->GetNextSerial(SerialTracker::EventType::OTHER_EVENT),
112 base::UTF16ToUTF8(text).c_str());
113 wl_client_flush(client());
114 }
115
SetCursor(const gfx::Range & selection)116 void SetCursor(const gfx::Range& selection) override {
117 zwp_text_input_v1_send_cursor_position(text_input_, selection.end(),
118 selection.start());
119 }
120
DeleteSurroundingText(const gfx::Range & range)121 void DeleteSurroundingText(const gfx::Range& range) override {
122 zwp_text_input_v1_send_delete_surrounding_text(text_input_, range.start(),
123 range.length());
124 }
125
SendKey(const ui::KeyEvent & event)126 void SendKey(const ui::KeyEvent& event) override {
127 uint32_t keysym = xkb_tracker_->GetKeysym(
128 ui::KeycodeConverter::DomCodeToNativeKeycode(event.code()));
129 bool pressed = (event.type() == ui::ET_KEY_PRESSED);
130 // TODO(mukai): consolidate the definition of this modifiers_mask with other
131 // similar ones in components/exo/keyboard.cc or arc_ime_service.cc.
132 constexpr uint32_t modifiers_mask =
133 ui::EF_SHIFT_DOWN | ui::EF_CONTROL_DOWN | ui::EF_ALT_DOWN |
134 ui::EF_COMMAND_DOWN | ui::EF_ALTGR_DOWN | ui::EF_MOD3_DOWN;
135 // 1-bit shifts to adjust the bitpattern for the modifiers; see also
136 // WaylandTextInputDelegate::SendModifiers().
137 uint32_t modifiers = (event.flags() & modifiers_mask) >> 1;
138
139 zwp_text_input_v1_send_keysym(
140 text_input_, TimeTicksToMilliseconds(event.time_stamp()),
141 serial_tracker_->GetNextSerial(SerialTracker::EventType::OTHER_EVENT),
142 keysym,
143 pressed ? WL_KEYBOARD_KEY_STATE_PRESSED
144 : WL_KEYBOARD_KEY_STATE_RELEASED,
145 modifiers);
146 wl_client_flush(client());
147 }
148
OnTextDirectionChanged(base::i18n::TextDirection direction)149 void OnTextDirectionChanged(base::i18n::TextDirection direction) override {
150 uint32_t wayland_direction = ZWP_TEXT_INPUT_V1_TEXT_DIRECTION_AUTO;
151 switch (direction) {
152 case base::i18n::RIGHT_TO_LEFT:
153 wayland_direction = ZWP_TEXT_INPUT_V1_TEXT_DIRECTION_LTR;
154 break;
155 case base::i18n::LEFT_TO_RIGHT:
156 wayland_direction = ZWP_TEXT_INPUT_V1_TEXT_DIRECTION_RTL;
157 break;
158 case base::i18n::UNKNOWN_DIRECTION:
159 LOG(ERROR) << "Unrecognized direction: " << direction;
160 }
161
162 zwp_text_input_v1_send_text_direction(
163 text_input_,
164 serial_tracker_->GetNextSerial(SerialTracker::EventType::OTHER_EVENT),
165 wayland_direction);
166 }
167
168 wl_resource* text_input_;
169 wl_resource* surface_ = nullptr;
170
171 // Owned by Seat, which is updated before calling the callbacks of this
172 // class.
173 const XkbTracker* const xkb_tracker_;
174
175 // Owned by Server, which always outlives this delegate.
176 SerialTracker* const serial_tracker_;
177 };
178
text_input_activate(wl_client * client,wl_resource * resource,wl_resource * seat,wl_resource * surface_resource)179 void text_input_activate(wl_client* client,
180 wl_resource* resource,
181 wl_resource* seat,
182 wl_resource* surface_resource) {
183 TextInput* text_input = GetUserDataAs<TextInput>(resource);
184 Surface* surface = GetUserDataAs<Surface>(surface_resource);
185 static_cast<WaylandTextInputDelegate*>(text_input->delegate())
186 ->set_surface(surface_resource);
187 text_input->Activate(surface);
188
189 // Sending modifiers.
190 constexpr const char* kModifierNames[] = {
191 XKB_MOD_NAME_SHIFT,
192 XKB_MOD_NAME_CTRL,
193 XKB_MOD_NAME_ALT,
194 XKB_MOD_NAME_LOGO,
195 "Mod5",
196 "Mod3",
197 };
198 wl_array modifiers;
199 wl_array_init(&modifiers);
200 for (const char* modifier : kModifierNames) {
201 char* p =
202 static_cast<char*>(wl_array_add(&modifiers, ::strlen(modifier) + 1));
203 ::strcpy(p, modifier);
204 }
205 zwp_text_input_v1_send_modifiers_map(resource, &modifiers);
206 wl_array_release(&modifiers);
207 }
208
text_input_deactivate(wl_client * client,wl_resource * resource,wl_resource * seat)209 void text_input_deactivate(wl_client* client,
210 wl_resource* resource,
211 wl_resource* seat) {
212 TextInput* text_input = GetUserDataAs<TextInput>(resource);
213 text_input->Deactivate();
214 }
215
text_input_show_input_panel(wl_client * client,wl_resource * resource)216 void text_input_show_input_panel(wl_client* client, wl_resource* resource) {
217 GetUserDataAs<TextInput>(resource)->ShowVirtualKeyboardIfEnabled();
218 }
219
text_input_hide_input_panel(wl_client * client,wl_resource * resource)220 void text_input_hide_input_panel(wl_client* client, wl_resource* resource) {
221 GetUserDataAs<TextInput>(resource)->HideVirtualKeyboard();
222 }
223
text_input_reset(wl_client * client,wl_resource * resource)224 void text_input_reset(wl_client* client, wl_resource* resource) {
225 GetUserDataAs<TextInput>(resource)->Resync();
226 }
227
text_input_set_surrounding_text(wl_client * client,wl_resource * resource,const char * text,uint32_t cursor,uint32_t anchor)228 void text_input_set_surrounding_text(wl_client* client,
229 wl_resource* resource,
230 const char* text,
231 uint32_t cursor,
232 uint32_t anchor) {
233 TextInput* text_input = GetUserDataAs<TextInput>(resource);
234 auto utf16_cursor = ui::Utf16OffsetFromUtf8Offset(text, cursor);
235 if (!utf16_cursor)
236 return;
237 auto utf16_anchor = ui::Utf16OffsetFromUtf8Offset(text, anchor);
238 if (!utf16_anchor)
239 return;
240 text_input->SetSurroundingText(base::UTF8ToUTF16(text), *utf16_cursor,
241 *utf16_anchor);
242 }
243
text_input_set_content_type(wl_client * client,wl_resource * resource,uint32_t hint,uint32_t purpose)244 void text_input_set_content_type(wl_client* client,
245 wl_resource* resource,
246 uint32_t hint,
247 uint32_t purpose) {
248 TextInput* text_input = GetUserDataAs<TextInput>(resource);
249 ui::TextInputType type = ui::TEXT_INPUT_TYPE_TEXT;
250 ui::TextInputMode mode = ui::TEXT_INPUT_MODE_DEFAULT;
251 int flags = ui::TEXT_INPUT_FLAG_NONE;
252 bool should_do_learning = true;
253 if (hint & ZWP_TEXT_INPUT_V1_CONTENT_HINT_AUTO_COMPLETION)
254 flags |= ui::TEXT_INPUT_FLAG_AUTOCOMPLETE_ON;
255 if (hint & ZWP_TEXT_INPUT_V1_CONTENT_HINT_AUTO_CORRECTION)
256 flags |= ui::TEXT_INPUT_FLAG_AUTOCORRECT_ON;
257 if (hint & ZWP_TEXT_INPUT_V1_CONTENT_HINT_AUTO_CAPITALIZATION)
258 flags |= ui::TEXT_INPUT_FLAG_AUTOCAPITALIZE_SENTENCES;
259 if (hint & ZWP_TEXT_INPUT_V1_CONTENT_HINT_LOWERCASE)
260 flags |= ui::TEXT_INPUT_FLAG_AUTOCAPITALIZE_NONE;
261 if (hint & ZWP_TEXT_INPUT_V1_CONTENT_HINT_UPPERCASE)
262 flags |= ui::TEXT_INPUT_FLAG_AUTOCAPITALIZE_CHARACTERS;
263 if (hint & ZWP_TEXT_INPUT_V1_CONTENT_HINT_TITLECASE)
264 flags |= ui::TEXT_INPUT_FLAG_AUTOCAPITALIZE_WORDS;
265 if (hint & ZWP_TEXT_INPUT_V1_CONTENT_HINT_HIDDEN_TEXT) {
266 flags |= ui::TEXT_INPUT_FLAG_AUTOCOMPLETE_OFF |
267 ui::TEXT_INPUT_FLAG_AUTOCORRECT_OFF;
268 }
269 if (hint & ZWP_TEXT_INPUT_V1_CONTENT_HINT_SENSITIVE_DATA)
270 should_do_learning = false;
271 // Unused hints: LATIN, MULTILINE.
272
273 switch (purpose) {
274 case ZWP_TEXT_INPUT_V1_CONTENT_PURPOSE_DIGITS:
275 mode = ui::TEXT_INPUT_MODE_DECIMAL;
276 type = ui::TEXT_INPUT_TYPE_NUMBER;
277 break;
278 case ZWP_TEXT_INPUT_V1_CONTENT_PURPOSE_NUMBER:
279 mode = ui::TEXT_INPUT_MODE_NUMERIC;
280 type = ui::TEXT_INPUT_TYPE_NUMBER;
281 break;
282 case ZWP_TEXT_INPUT_V1_CONTENT_PURPOSE_PHONE:
283 mode = ui::TEXT_INPUT_MODE_TEL;
284 type = ui::TEXT_INPUT_TYPE_TELEPHONE;
285 break;
286 case ZWP_TEXT_INPUT_V1_CONTENT_PURPOSE_URL:
287 mode = ui::TEXT_INPUT_MODE_URL;
288 type = ui::TEXT_INPUT_TYPE_URL;
289 break;
290 case ZWP_TEXT_INPUT_V1_CONTENT_PURPOSE_EMAIL:
291 mode = ui::TEXT_INPUT_MODE_EMAIL;
292 type = ui::TEXT_INPUT_TYPE_EMAIL;
293 break;
294 case ZWP_TEXT_INPUT_V1_CONTENT_PURPOSE_PASSWORD:
295 DCHECK(!should_do_learning);
296 type = ui::TEXT_INPUT_TYPE_PASSWORD;
297 break;
298 case ZWP_TEXT_INPUT_V1_CONTENT_PURPOSE_DATE:
299 type = ui::TEXT_INPUT_TYPE_DATE;
300 break;
301 case ZWP_TEXT_INPUT_V1_CONTENT_PURPOSE_TIME:
302 type = ui::TEXT_INPUT_TYPE_TIME;
303 break;
304 case ZWP_TEXT_INPUT_V1_CONTENT_PURPOSE_DATETIME:
305 type = ui::TEXT_INPUT_TYPE_DATE_TIME;
306 break;
307 case ZWP_TEXT_INPUT_V1_CONTENT_PURPOSE_NORMAL:
308 case ZWP_TEXT_INPUT_V1_CONTENT_PURPOSE_ALPHA:
309 case ZWP_TEXT_INPUT_V1_CONTENT_PURPOSE_NAME:
310 case ZWP_TEXT_INPUT_V1_CONTENT_PURPOSE_TERMINAL:
311 // No special type / mode are set.
312 break;
313 }
314
315 text_input->SetTypeModeFlags(type, mode, flags, should_do_learning);
316 }
317
text_input_set_cursor_rectangle(wl_client * client,wl_resource * resource,int32_t x,int32_t y,int32_t width,int32_t height)318 void text_input_set_cursor_rectangle(wl_client* client,
319 wl_resource* resource,
320 int32_t x,
321 int32_t y,
322 int32_t width,
323 int32_t height) {
324 GetUserDataAs<TextInput>(resource)->SetCaretBounds(
325 gfx::Rect(x, y, width, height));
326 }
327
text_input_set_preferred_language(wl_client * client,wl_resource * resource,const char * language)328 void text_input_set_preferred_language(wl_client* client,
329 wl_resource* resource,
330 const char* language) {
331 // Nothing needs to be done.
332 }
333
text_input_commit_state(wl_client * client,wl_resource * resource,uint32_t serial)334 void text_input_commit_state(wl_client* client,
335 wl_resource* resource,
336 uint32_t serial) {
337 // Nothing needs to be done.
338 }
339
text_input_invoke_action(wl_client * client,wl_resource * resource,uint32_t button,uint32_t index)340 void text_input_invoke_action(wl_client* client,
341 wl_resource* resource,
342 uint32_t button,
343 uint32_t index) {
344 GetUserDataAs<TextInput>(resource)->Resync();
345 }
346
347 const struct zwp_text_input_v1_interface text_input_v1_implementation = {
348 text_input_activate,
349 text_input_deactivate,
350 text_input_show_input_panel,
351 text_input_hide_input_panel,
352 text_input_reset,
353 text_input_set_surrounding_text,
354 text_input_set_content_type,
355 text_input_set_cursor_rectangle,
356 text_input_set_preferred_language,
357 text_input_commit_state,
358 text_input_invoke_action,
359 };
360
361 ////////////////////////////////////////////////////////////////////////////////
362 // text_input_manager_v1 interface:
363
text_input_manager_create_text_input(wl_client * client,wl_resource * resource,uint32_t id)364 void text_input_manager_create_text_input(wl_client* client,
365 wl_resource* resource,
366 uint32_t id) {
367 auto* data = GetUserDataAs<WaylandTextInputManager>(resource);
368
369 wl_resource* text_input_resource =
370 wl_resource_create(client, &zwp_text_input_v1_interface, 1, id);
371
372 SetImplementation(
373 text_input_resource, &text_input_v1_implementation,
374 std::make_unique<TextInput>(std::make_unique<WaylandTextInputDelegate>(
375 text_input_resource, data->xkb_tracker, data->serial_tracker)));
376 }
377
378 const struct zwp_text_input_manager_v1_interface
379 text_input_manager_implementation = {
380 text_input_manager_create_text_input,
381 };
382
383 } // namespace
384
bind_text_input_manager(wl_client * client,void * data,uint32_t version,uint32_t id)385 void bind_text_input_manager(wl_client* client,
386 void* data,
387 uint32_t version,
388 uint32_t id) {
389 wl_resource* resource =
390 wl_resource_create(client, &zwp_text_input_manager_v1_interface, 1, id);
391 wl_resource_set_implementation(resource, &text_input_manager_implementation,
392 data, nullptr);
393 }
394
395 } // namespace wayland
396 } // namespace exo
397