1 /* * This file is part of Maliit framework *
2 *
3 * Copyright (C) 2012 Canonical Ltd
4 *
5 *
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License version 2.1 as published by the Free Software Foundation
9 * and appearing in the file LICENSE.LGPL included in the packaging
10 * of this file.
11 */
12
13 #include <cerrno> // for errno
14 #include <cstring> // for strerror
15 #include <QGuiApplication>
16 #include <QKeyEvent>
17 #include <qpa/qplatformnativeinterface.h>
18
19 #include "wayland-client.h"
20 #include <qwayland-input-method-unstable-v1.h>
21 #include <qwayland-text-input-unstable-v1.h>
22
23 #include <xkbcommon/xkbcommon.h>
24
25 #include "waylandinputmethodconnection.h"
26
27 Q_LOGGING_CATEGORY(lcWaylandConnection, "maliit.connection.wayland")
28
29 namespace {
30
31 // TODO: Deduplicate it. Those values are used in
32 // minputcontextconnection, mimpluginmanager,
33 // mattributeextensionmanager and in input context implementations.
34 const char * const FocusStateAttribute = "focusState";
35 const char * const ContentTypeAttribute = "contentType";
36 const char * const CorrectionAttribute = "correctionEnabled";
37 const char * const PredictionAttribute = "predictionEnabled";
38 const char * const AutoCapitalizationAttribute = "autocapitalizationEnabled";
39 const char * const SurroundingTextAttribute = "surroundingText";
40 const char * const AnchorPositionAttribute = "anchorPosition";
41 const char * const CursorPositionAttribute = "cursorPosition";
42 const char * const HasSelectionAttribute = "hasSelection";
43 const char * const HiddenTextAttribute = "hiddenText";
44
45 typedef QPair<Qt::KeyboardModifiers, const char *> Modifier;
46 const Modifier modifiers[] = {
47 Modifier(Qt::ShiftModifier, XKB_MOD_NAME_SHIFT),
48 Modifier(Qt::ControlModifier, XKB_MOD_NAME_CTRL),
49 Modifier(Qt::AltModifier, XKB_MOD_NAME_ALT),
50 Modifier(Qt::MetaModifier, XKB_MOD_NAME_LOGO),
51 Modifier(Qt::KeypadModifier, XKB_LED_NAME_NUM)
52 };
53
modifiersMap()54 QByteArray modifiersMap()
55 {
56 QByteArray mod_map;
57 for (unsigned int iter(0); iter < (sizeof(modifiers) / sizeof(modifiers[0])); ++iter) {
58 mod_map.append(modifiers[iter].second);
59 }
60 return mod_map;
61 }
62
modifiersFromQt(const Qt::KeyboardModifiers qt_mods)63 xkb_mod_mask_t modifiersFromQt(const Qt::KeyboardModifiers qt_mods)
64 {
65 xkb_mod_mask_t mod_mask(0);
66
67 if (qt_mods == Qt::NoModifier) {
68 return mod_mask;
69 }
70
71 for (unsigned int iter(0); iter < (sizeof(modifiers) / sizeof(modifiers[0])); ++iter) {
72 if ((qt_mods & modifiers[iter].first) == modifiers[iter].first) {
73 mod_mask |= 1 << iter;
74 }
75 }
76
77 return mod_mask;
78 }
79
keyFromQt(int qt_key)80 xkb_keysym_t keyFromQt(int qt_key)
81 {
82 switch (qt_key) {
83 case Qt::Key_Backspace:
84 return XKB_KEY_BackSpace;
85 case Qt::Key_Return:
86 return XKB_KEY_Return;
87 case Qt::Key_Left:
88 return XKB_KEY_Left;
89 case Qt::Key_Up:
90 return XKB_KEY_Up;
91 case Qt::Key_Right:
92 return XKB_KEY_Right;
93 case Qt::Key_Down:
94 return XKB_KEY_Down;
95 default:
96 return XKB_KEY_NoSymbol;
97 }
98 }
99
preeditStyleFromMaliit(Maliit::PreeditFace face)100 QtWayland::zwp_text_input_v1::preedit_style preeditStyleFromMaliit(Maliit::PreeditFace face)
101 {
102 switch (face) {
103 case Maliit::PreeditDefault:
104 return QtWayland::zwp_text_input_v1::preedit_style_default;
105 case Maliit::PreeditNoCandidates:
106 return QtWayland::zwp_text_input_v1::preedit_style_incorrect;
107 case Maliit::PreeditKeyPress:
108 return QtWayland::zwp_text_input_v1::preedit_style_highlight;
109 case Maliit::PreeditUnconvertible:
110 return QtWayland::zwp_text_input_v1::preedit_style_inactive;
111 case Maliit::PreeditActive:
112 return QtWayland::zwp_text_input_v1::preedit_style_active;
113 default:
114 return QtWayland::zwp_text_input_v1::preedit_style_none;
115 }
116 }
117
contentTypeFromWayland(uint32_t purpose)118 Maliit::TextContentType contentTypeFromWayland(uint32_t purpose)
119 {
120 switch (purpose) {
121 case QtWayland::zwp_text_input_v1::content_purpose_normal:
122 return Maliit::FreeTextContentType;
123 case QtWayland::zwp_text_input_v1::content_purpose_digits:
124 case QtWayland::zwp_text_input_v1::content_purpose_number:
125 return Maliit::NumberContentType;
126 case QtWayland::zwp_text_input_v1::content_purpose_phone:
127 return Maliit::PhoneNumberContentType;
128 case QtWayland::zwp_text_input_v1::content_purpose_url:
129 return Maliit::UrlContentType;
130 case QtWayland::zwp_text_input_v1::content_purpose_email:
131 return Maliit::EmailContentType;
132 default:
133 return Maliit::CustomContentType;
134 }
135 }
136
matchesFlag(int value,int flag)137 bool matchesFlag(int value,
138 int flag)
139 {
140 return ((value & flag) == flag);
141 }
142
143 const unsigned int wayland_connection_id(1);
144
145 } // unnamed namespace
146
147 namespace Maliit {
148 namespace Wayland {
149
150 class InputMethodContext;
151
152 class InputMethod : public QtWayland::zwp_input_method_v1
153 {
154 public:
155 InputMethod(MInputContextConnection *connection, struct wl_registry *registry, int id);
156 ~InputMethod();
157
158 InputMethodContext *context() const;
159
160 protected:
161 void zwp_input_method_v1_activate(struct ::zwp_input_method_context_v1 *id) Q_DECL_OVERRIDE;
162 void zwp_input_method_v1_deactivate(struct ::zwp_input_method_context_v1 *context) Q_DECL_OVERRIDE;
163
164 private:
165 MInputContextConnection *m_connection;
166 QScopedPointer<InputMethodContext> m_context;
167 };
168
169 class InputMethodContext : public QtWayland::zwp_input_method_context_v1
170 {
171 public:
172 InputMethodContext(MInputContextConnection *connection, struct ::zwp_input_method_context_v1 *object);
173 ~InputMethodContext();
174
175 QString selection() const;
176 uint32_t serial() const;
177
178 protected:
179 void zwp_input_method_context_v1_commit_state(uint32_t serial) Q_DECL_OVERRIDE;
180 void zwp_input_method_context_v1_content_type(uint32_t hint, uint32_t purpose) Q_DECL_OVERRIDE;
181 void zwp_input_method_context_v1_invoke_action(uint32_t button, uint32_t index) Q_DECL_OVERRIDE;
182 void zwp_input_method_context_v1_preferred_language(const QString &language) Q_DECL_OVERRIDE;
183 void zwp_input_method_context_v1_reset() Q_DECL_OVERRIDE;
184 void zwp_input_method_context_v1_surrounding_text(const QString &text, uint32_t cursor, uint32_t anchor) Q_DECL_OVERRIDE;
185
186 private:
187 MInputContextConnection *m_connection;
188 QVariantMap m_stateInfo;
189 uint32_t m_serial;
190 QString m_selection;
191 };
192
193 }
194 }
195
196 struct WaylandInputMethodConnectionPrivate
197 {
198 Q_DECLARE_PUBLIC(WaylandInputMethodConnection)
199
200 WaylandInputMethodConnectionPrivate(WaylandInputMethodConnection *connection);
201 ~WaylandInputMethodConnectionPrivate();
202
203 void handleRegistryGlobal(uint32_t name,
204 const char *interface,
205 uint32_t version);
206 void handleRegistryGlobalRemove(uint32_t name);
207
208 Maliit::Wayland::InputMethodContext *context();
209
210 WaylandInputMethodConnection *q_ptr;
211 wl_display *display;
212 wl_registry *registry;
213 QScopedPointer<Maliit::Wayland::InputMethod> input_method;
214 };
215
216 namespace {
217
registryGlobal(void * data,wl_registry * registry,uint32_t name,const char * interface,uint32_t version)218 void registryGlobal(void *data,
219 wl_registry *registry,
220 uint32_t name,
221 const char *interface,
222 uint32_t version)
223 {
224 WaylandInputMethodConnectionPrivate *d =
225 static_cast<WaylandInputMethodConnectionPrivate *>(data);
226
227 Q_UNUSED(registry);
228 d->handleRegistryGlobal(name, interface, version);
229 }
230
registryGlobalRemove(void * data,wl_registry * registry,uint32_t name)231 void registryGlobalRemove(void *data,
232 wl_registry *registry,
233 uint32_t name)
234 {
235 WaylandInputMethodConnectionPrivate *d =
236 static_cast<WaylandInputMethodConnectionPrivate *>(data);
237
238 Q_UNUSED(registry);
239 d->handleRegistryGlobalRemove(name);
240 }
241
242 const wl_registry_listener maliit_registry_listener = {
243 registryGlobal,
244 registryGlobalRemove
245 };
246
247
248 } // unnamed namespace
249
WaylandInputMethodConnectionPrivate(WaylandInputMethodConnection * connection)250 WaylandInputMethodConnectionPrivate::WaylandInputMethodConnectionPrivate(WaylandInputMethodConnection *connection)
251 : q_ptr(connection),
252 display(0),
253 registry(0),
254 input_method()
255 {
256 display = static_cast<wl_display *>(QGuiApplication::platformNativeInterface()->nativeResourceForIntegration("display"));
257 if (!display) {
258 qCritical() << Q_FUNC_INFO << "Failed to get a display.";
259 return;
260 }
261 registry = wl_display_get_registry(display);
262 wl_registry_add_listener(registry, &maliit_registry_listener, this);
263 }
264
~WaylandInputMethodConnectionPrivate()265 WaylandInputMethodConnectionPrivate::~WaylandInputMethodConnectionPrivate()
266 {
267 input_method.reset();
268 if (registry) {
269 wl_registry_destroy(registry);
270 }
271 }
272
handleRegistryGlobal(uint32_t name,const char * interface,uint32_t version)273 void WaylandInputMethodConnectionPrivate::handleRegistryGlobal(uint32_t name,
274 const char *interface,
275 uint32_t version)
276 {
277 Q_UNUSED(version);
278 Q_Q(WaylandInputMethodConnection);
279
280 if (!strcmp(interface, "zwp_input_method_v1")) {
281 input_method.reset(new Maliit::Wayland::InputMethod(q, registry, name));
282 }
283 }
284
handleRegistryGlobalRemove(uint32_t name)285 void WaylandInputMethodConnectionPrivate::handleRegistryGlobalRemove(uint32_t name)
286 {
287 qCDebug(lcWaylandConnection) << Q_FUNC_INFO << name;
288 }
289
context()290 Maliit::Wayland::InputMethodContext *WaylandInputMethodConnectionPrivate::context()
291 {
292 return input_method ? input_method->context() : 0;
293 }
294
295 // MInputContextWestonIMProtocolConnection
296
WaylandInputMethodConnection()297 WaylandInputMethodConnection::WaylandInputMethodConnection()
298 : d_ptr(new WaylandInputMethodConnectionPrivate(this))
299 {
300 }
301
~WaylandInputMethodConnection()302 WaylandInputMethodConnection::~WaylandInputMethodConnection()
303 {
304 }
305
sendPreeditString(const QString & string,const QList<Maliit::PreeditTextFormat> & preedit_formats,int replace_start,int replace_length,int cursor_pos)306 void WaylandInputMethodConnection::sendPreeditString(const QString &string,
307 const QList<Maliit::PreeditTextFormat> &preedit_formats,
308 int replace_start,
309 int replace_length,
310 int cursor_pos)
311 {
312 Q_D(WaylandInputMethodConnection);
313
314 qCDebug(lcWaylandConnection) << Q_FUNC_INFO << string << replace_start << replace_length << cursor_pos;
315
316 if (!d->context())
317 return;
318
319 MInputContextConnection::sendPreeditString(string, preedit_formats,
320 replace_start, replace_length,
321 cursor_pos);
322
323 if (replace_length > 0) {
324 int cursor = widgetState().value(CursorPositionAttribute).toInt();
325 uint32_t index = string.midRef(qMin(cursor + replace_start, cursor), qAbs(replace_start)).toUtf8().size();
326 uint32_t length = string.midRef(cursor + replace_start, replace_length).toUtf8().size();
327 d->context()->delete_surrounding_text(index, length);
328 }
329
330 Q_FOREACH (const Maliit::PreeditTextFormat& format, preedit_formats) {
331 QtWayland::zwp_text_input_v1::preedit_style style = preeditStyleFromMaliit(format.preeditFace);
332 uint32_t index = string.leftRef(format.start).toUtf8().size();
333 uint32_t length = string.leftRef(format.start + format.length).toUtf8().size() - index;
334 qCDebug(lcWaylandConnection) << Q_FUNC_INFO << "preedit_styling" << index << length;
335 d->context()->preedit_styling(index, length, style);
336 }
337
338 // TODO check if defined like that/required
339 if (cursor_pos < 0) {
340 cursor_pos = string.size() + 1 - cursor_pos;
341 }
342
343 qCDebug(lcWaylandConnection) << Q_FUNC_INFO << "preedit_cursor" << string.leftRef(cursor_pos).toUtf8().size();
344 d->context()->preedit_cursor(string.leftRef(cursor_pos).toUtf8().size());
345 qCDebug(lcWaylandConnection) << Q_FUNC_INFO << "preedit_string" << string;
346 d->context()->preedit_string(d->context()->serial(), string, string);
347 }
348
349
sendCommitString(const QString & string,int replace_start,int replace_length,int cursor_pos)350 void WaylandInputMethodConnection::sendCommitString(const QString &string,
351 int replace_start,
352 int replace_length,
353 int cursor_pos)
354 {
355 Q_D(WaylandInputMethodConnection);
356
357 qCDebug(lcWaylandConnection) << Q_FUNC_INFO << string << replace_start << replace_length << cursor_pos;
358
359 if (!d->context())
360 return;
361
362 MInputContextConnection::sendCommitString(string, replace_start, replace_length, cursor_pos);
363
364 if (cursor_pos != 0) {
365 qCWarning(lcWaylandConnection) << Q_FUNC_INFO << "cursor_pos:" << cursor_pos << "!= 0 not supported yet";
366 cursor_pos = 0;
367 }
368
369 if (replace_length > 0) {
370 int cursor = widgetState().value(CursorPositionAttribute).toInt();
371 uint32_t index = string.midRef(qMin(cursor + replace_start, cursor), qAbs(replace_start)).toUtf8().size();
372 uint32_t length = string.midRef(cursor + replace_start, replace_length).toUtf8().size();
373 d->context()->delete_surrounding_text(index, length);
374 }
375
376 cursor_pos = string.leftRef(cursor_pos).toUtf8().size();
377 d->context()->cursor_position(cursor_pos, cursor_pos);
378 d->context()->commit_string(d->context()->serial(), string);
379 }
380
sendKeyEvent(const QKeyEvent & keyEvent,Maliit::EventRequestType requestType)381 void WaylandInputMethodConnection::sendKeyEvent(const QKeyEvent &keyEvent,
382 Maliit::EventRequestType requestType)
383 {
384 Q_D(WaylandInputMethodConnection);
385
386 qCDebug(lcWaylandConnection) << Q_FUNC_INFO;
387
388 if (!d->context())
389 return;
390
391 xkb_keysym_t sym(keyFromQt(keyEvent.key()));
392
393 if (sym == XKB_KEY_NoSymbol) {
394 qCWarning(lcWaylandConnection) << "No conversion from Qt::Key:" << keyEvent.key() << "to XKB key. Update the keyFromQt() function.";
395 return;
396 }
397
398 wl_keyboard_key_state state;
399
400 switch (keyEvent.type()) {
401 case QEvent::KeyPress:
402 state = WL_KEYBOARD_KEY_STATE_PRESSED;
403 break;
404
405 case QEvent::KeyRelease:
406 state = WL_KEYBOARD_KEY_STATE_RELEASED;
407 break;
408
409 default:
410 qCWarning(lcWaylandConnection) << "Unknown QKeyEvent type:" << keyEvent.type();
411 return;
412 }
413
414 xkb_mod_mask_t modifiers(modifiersFromQt(keyEvent.modifiers()));
415
416 MInputContextConnection::sendKeyEvent(keyEvent, requestType);
417
418 d->context()->keysym(d->context()->serial(),
419 keyEvent.timestamp(),
420 sym, state, modifiers);
421 }
422
selection(bool & valid)423 QString WaylandInputMethodConnection::selection(bool &valid)
424 {
425 Q_D(WaylandInputMethodConnection);
426
427 qCDebug(lcWaylandConnection) << Q_FUNC_INFO;
428
429 Maliit::Wayland::InputMethodContext *context = d->input_method->context();
430
431 valid = context && !context->selection().isEmpty();
432 return context ? context->selection() : QString();
433 }
434
setLanguage(const QString & language)435 void WaylandInputMethodConnection::setLanguage(const QString &language)
436 {
437 Q_D(WaylandInputMethodConnection);
438
439 qCDebug(lcWaylandConnection) << Q_FUNC_INFO;
440
441 if (!d->context())
442 return;
443
444 d->context()->language(d->context()->serial(), language);
445 }
446
setSelection(int start,int length)447 void WaylandInputMethodConnection::setSelection(int start, int length)
448 {
449 Q_D (WaylandInputMethodConnection);
450
451 qCDebug(lcWaylandConnection) << Q_FUNC_INFO;
452
453 if (!d->context())
454 return;
455
456 QString surrounding = widgetState().value(SurroundingTextAttribute).toString();
457 uint32_t index(surrounding.leftRef(start + length).toUtf8().size());
458 uint32_t anchor(surrounding.leftRef(start).toUtf8().size());
459
460 d->context()->cursor_position(index, anchor);
461 d->context()->commit_string(d->context()->serial(), QString());
462 }
463
464 namespace Maliit {
465 namespace Wayland {
466
InputMethod(MInputContextConnection * connection,struct wl_registry * registry,int id)467 InputMethod::InputMethod(MInputContextConnection *connection, struct wl_registry *registry, int id)
468 : QtWayland::zwp_input_method_v1(registry, id, 1)
469 , m_connection(connection)
470 , m_context()
471 {
472 qCDebug(lcWaylandConnection) << Q_FUNC_INFO;
473 }
474
~InputMethod()475 InputMethod::~InputMethod()
476 {
477 }
478
context() const479 InputMethodContext *InputMethod::context() const
480 {
481 return m_context.data();
482 }
483
zwp_input_method_v1_activate(struct::zwp_input_method_context_v1 * id)484 void InputMethod::zwp_input_method_v1_activate(struct ::zwp_input_method_context_v1 *id)
485 {
486 qCDebug(lcWaylandConnection) << Q_FUNC_INFO;
487
488 m_context.reset(new InputMethodContext(m_connection, id));
489
490 m_context->modifiers_map(modifiersMap());
491
492 }
493
zwp_input_method_v1_deactivate(struct zwp_input_method_context_v1 *)494 void InputMethod::zwp_input_method_v1_deactivate(struct zwp_input_method_context_v1 *)
495 {
496 qCDebug(lcWaylandConnection) << Q_FUNC_INFO;
497
498 m_context.reset();
499
500 m_connection->handleDisconnection(wayland_connection_id);
501 }
502
InputMethodContext(MInputContextConnection * connection,struct::zwp_input_method_context_v1 * object)503 InputMethodContext::InputMethodContext(MInputContextConnection *connection, struct ::zwp_input_method_context_v1 *object)
504 : QtWayland::zwp_input_method_context_v1(object)
505 , m_connection(connection)
506 , m_stateInfo()
507 , m_serial(0)
508 , m_selection()
509 {
510 qCDebug(lcWaylandConnection) << Q_FUNC_INFO;
511
512 m_stateInfo[FocusStateAttribute] = true;
513 m_connection->activateContext(wayland_connection_id);
514 m_connection->showInputMethod(wayland_connection_id);
515 }
516
~InputMethodContext()517 InputMethodContext::~InputMethodContext()
518 {
519 qCDebug(lcWaylandConnection) << Q_FUNC_INFO;
520
521 m_stateInfo.clear();
522 m_stateInfo[FocusStateAttribute] = false;
523 m_connection->updateWidgetInformation(wayland_connection_id, m_stateInfo, true);
524 m_connection->hideInputMethod(wayland_connection_id);
525 }
526
selection() const527 QString InputMethodContext::selection() const
528 {
529 return m_selection;
530 }
531
serial() const532 uint32_t InputMethodContext::serial() const
533 {
534 return m_serial;
535 }
536
zwp_input_method_context_v1_commit_state(uint32_t serial)537 void InputMethodContext::zwp_input_method_context_v1_commit_state(uint32_t serial)
538 {
539 qCDebug(lcWaylandConnection) << Q_FUNC_INFO;
540
541 m_serial = serial;
542 m_connection->updateWidgetInformation(wayland_connection_id, m_stateInfo, false);
543 }
544
zwp_input_method_context_v1_content_type(uint32_t hint,uint32_t purpose)545 void InputMethodContext::zwp_input_method_context_v1_content_type(uint32_t hint, uint32_t purpose)
546 {
547 qCDebug(lcWaylandConnection) << Q_FUNC_INFO;
548
549 m_stateInfo[ContentTypeAttribute] = contentTypeFromWayland(purpose);
550 m_stateInfo[AutoCapitalizationAttribute] = matchesFlag(hint, QtWayland::zwp_text_input_v1::content_hint_auto_capitalization);
551 m_stateInfo[CorrectionAttribute] = matchesFlag(hint, QtWayland::zwp_text_input_v1::content_hint_auto_correction);
552 m_stateInfo[PredictionAttribute] = matchesFlag(hint, QtWayland::zwp_text_input_v1::content_hint_auto_completion);
553 m_stateInfo[HiddenTextAttribute] = matchesFlag(hint, QtWayland::zwp_text_input_v1::content_hint_hidden_text);
554 }
555
zwp_input_method_context_v1_invoke_action(uint32_t button,uint32_t index)556 void InputMethodContext::zwp_input_method_context_v1_invoke_action(uint32_t button, uint32_t index)
557 {
558 qCDebug(lcWaylandConnection) << Q_FUNC_INFO << button << index;
559 }
560
zwp_input_method_context_v1_preferred_language(const QString & language)561 void InputMethodContext::zwp_input_method_context_v1_preferred_language(const QString &language)
562 {
563 qCDebug(lcWaylandConnection) << Q_FUNC_INFO << language;
564 }
565
zwp_input_method_context_v1_reset()566 void InputMethodContext::zwp_input_method_context_v1_reset()
567 {
568 qCDebug(lcWaylandConnection) << Q_FUNC_INFO;
569
570 m_connection->reset(wayland_connection_id);
571 m_connection->showInputMethod(wayland_connection_id);
572 }
573
zwp_input_method_context_v1_surrounding_text(const QString & text,uint32_t cursor,uint32_t anchor)574 void InputMethodContext::zwp_input_method_context_v1_surrounding_text(const QString &text, uint32_t cursor, uint32_t anchor)
575 {
576 qCDebug(lcWaylandConnection) << Q_FUNC_INFO;
577
578 const QByteArray &utf8_text(text.toUtf8());
579
580 m_stateInfo[SurroundingTextAttribute] = text;
581 m_stateInfo[CursorPositionAttribute] = QString::fromUtf8(utf8_text.constData(), cursor).size();
582 m_stateInfo[AnchorPositionAttribute] = QString::fromUtf8(utf8_text.constData(), anchor).size();
583 if (cursor == anchor) {
584 m_stateInfo[HasSelectionAttribute] = false;
585 m_selection.clear();
586 } else {
587 m_stateInfo[HasSelectionAttribute] = true;
588 uint32_t begin = qMin(anchor, cursor);
589 uint32_t end = qMax(anchor, cursor);
590 m_selection = QString::fromUtf8(utf8_text.constData() + begin, end - begin);
591 }
592 }
593
594 }
595 }
596