1 | // Copyright (C) 2020 The Qt Company Ltd. |
2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only |
3 | |
4 | #include "qwaylandqttextinputmethod.h" |
5 | #include "qwaylandqttextinputmethod_p.h" |
6 | |
7 | #include <QtGui/qevent.h> |
8 | #include <QtGui/qguiapplication.h> |
9 | #include <QtGui/qinputmethod.h> |
10 | #include <QtGui/qcolor.h> |
11 | #include <QtGui/qtextformat.h> |
12 | |
13 | #include <QtWaylandCompositor/qwaylandcompositor.h> |
14 | #include <QtWaylandCompositor/qwaylandsurface.h> |
15 | |
16 | QT_BEGIN_NAMESPACE |
17 | |
18 | QWaylandQtTextInputMethodPrivate::QWaylandQtTextInputMethodPrivate(QWaylandCompositor *c) |
19 | : compositor(c) |
20 | { |
21 | } |
22 | |
23 | void QWaylandQtTextInputMethodPrivate::text_input_method_v1_enable(Resource *resource, struct ::wl_resource *surface) |
24 | { |
25 | Q_Q(QWaylandQtTextInputMethod); |
26 | if (this->resource == resource) { |
27 | QWaylandSurface *waylandSurface = QWaylandSurface::fromResource(resource: surface); |
28 | if (surface != nullptr) { |
29 | enabledSurfaces[resource] = waylandSurface; |
30 | emit q->surfaceEnabled(surface: waylandSurface); |
31 | } |
32 | } |
33 | } |
34 | |
35 | void QWaylandQtTextInputMethodPrivate::text_input_method_v1_disable(Resource *resource, struct ::wl_resource *surface) |
36 | { |
37 | Q_Q(QWaylandQtTextInputMethod); |
38 | if (this->resource == resource) { |
39 | QWaylandSurface *waylandSurface = QWaylandSurface::fromResource(resource: surface); |
40 | QWaylandSurface *enabledSurface = enabledSurfaces.take(resource); |
41 | |
42 | if (Q_UNLIKELY(enabledSurface != waylandSurface)) |
43 | qCWarning(qLcWaylandCompositorInputMethods) << "Disabled surface does not match the one currently enabled" ; |
44 | |
45 | emit q->surfaceEnabled(surface: waylandSurface); |
46 | } |
47 | } |
48 | |
49 | void QWaylandQtTextInputMethodPrivate::text_input_method_v1_destroy(Resource *resource) |
50 | { |
51 | if (this->resource == resource) |
52 | wl_resource_destroy(resource->handle); |
53 | } |
54 | |
55 | void QWaylandQtTextInputMethodPrivate::text_input_method_v1_reset(Resource *resource) |
56 | { |
57 | if (this->resource == resource) |
58 | qApp->inputMethod()->reset(); |
59 | } |
60 | |
61 | void QWaylandQtTextInputMethodPrivate::text_input_method_v1_commit(Resource *resource) |
62 | { |
63 | if (this->resource == resource) |
64 | qApp->inputMethod()->commit(); |
65 | } |
66 | |
67 | void QWaylandQtTextInputMethodPrivate::text_input_method_v1_show_input_panel(Resource *resource) |
68 | { |
69 | if (this->resource == resource) |
70 | qApp->inputMethod()->show(); |
71 | } |
72 | |
73 | void QWaylandQtTextInputMethodPrivate::text_input_method_v1_hide_input_panel(Resource *resource) |
74 | { |
75 | if (this->resource == resource) |
76 | qApp->inputMethod()->hide(); |
77 | } |
78 | |
79 | void QWaylandQtTextInputMethodPrivate::text_input_method_v1_invoke_action(Resource *resource, int32_t type, int32_t cursorPosition) |
80 | { |
81 | if (this->resource == resource) |
82 | qApp->inputMethod()->invokeAction(a: QInputMethod::Action(type), cursorPosition); |
83 | } |
84 | |
85 | void QWaylandQtTextInputMethodPrivate::text_input_method_v1_update_cursor_rectangle(Resource *resource, int32_t x, int32_t y, int32_t width, int32_t height) |
86 | { |
87 | if (this->resource == resource) |
88 | cursorRectangle = QRect(x, y, width, height); |
89 | } |
90 | |
91 | void QWaylandQtTextInputMethodPrivate::text_input_method_v1_start_update(Resource *resource, int32_t queries) |
92 | { |
93 | if (this->resource == resource) |
94 | updatingQueries = Qt::InputMethodQueries(queries); |
95 | } |
96 | |
97 | void QWaylandQtTextInputMethodPrivate::text_input_method_v1_update_hints(Resource *resource, int32_t hints) |
98 | { |
99 | if (this->resource == resource) |
100 | this->hints = Qt::InputMethodHints(hints); |
101 | } |
102 | |
103 | void QWaylandQtTextInputMethodPrivate::text_input_method_v1_update_anchor_position(Resource *resource, int32_t anchorPosition) |
104 | { |
105 | if (this->resource == resource) |
106 | this->anchorPosition = anchorPosition; |
107 | } |
108 | |
109 | void QWaylandQtTextInputMethodPrivate::text_input_method_v1_update_cursor_position(Resource *resource, int32_t cursorPosition) |
110 | { |
111 | if (this->resource == resource) |
112 | this->cursorPosition = cursorPosition; |
113 | } |
114 | |
115 | void QWaylandQtTextInputMethodPrivate::text_input_method_v1_update_surrounding_text(Resource *resource, const QString &surroundingText, int32_t surroundingTextOffset) |
116 | { |
117 | if (this->resource == resource) { |
118 | this->surroundingText = surroundingText; |
119 | this->surroundingTextOffset = surroundingTextOffset; |
120 | } |
121 | } |
122 | |
123 | void QWaylandQtTextInputMethodPrivate::text_input_method_v1_update_absolute_position(Resource *resource, int32_t absolutePosition) |
124 | { |
125 | if (this->resource == resource) |
126 | this->absolutePosition = absolutePosition; |
127 | } |
128 | |
129 | void QWaylandQtTextInputMethodPrivate::text_input_method_v1_update_preferred_language(Resource *resource, const QString &preferredLanguage) |
130 | { |
131 | if (this->resource == resource) |
132 | this->preferredLanguage = preferredLanguage; |
133 | } |
134 | |
135 | void QWaylandQtTextInputMethodPrivate::text_input_method_v1_end_update(Resource *resource) |
136 | { |
137 | Q_Q(QWaylandQtTextInputMethod); |
138 | if (this->resource == resource && updatingQueries != 0) { |
139 | Qt::InputMethodQueries queries = updatingQueries; |
140 | updatingQueries = Qt::InputMethodQueries(); |
141 | emit q->updateInputMethod(queries); |
142 | } |
143 | } |
144 | |
145 | void QWaylandQtTextInputMethodPrivate::text_input_method_v1_acknowledge_input_method(Resource *resource) |
146 | { |
147 | if (this->resource == resource) |
148 | waitingForSync = false; |
149 | } |
150 | |
151 | QWaylandQtTextInputMethod::QWaylandQtTextInputMethod(QWaylandObject *container, QWaylandCompositor *compositor) |
152 | : QWaylandCompositorExtensionTemplate(container, *new QWaylandQtTextInputMethodPrivate(compositor)) |
153 | { |
154 | connect(sender: &d_func()->focusDestroyListener, signal: &QWaylandDestroyListener::fired, |
155 | context: this, slot: &QWaylandQtTextInputMethod::focusSurfaceDestroyed); |
156 | |
157 | connect(qGuiApp->inputMethod(), signal: &QInputMethod::visibleChanged, context: this, slot: &QWaylandQtTextInputMethod::sendVisibleChanged); |
158 | connect(qGuiApp->inputMethod(), signal: &QInputMethod::keyboardRectangleChanged, context: this, slot: &QWaylandQtTextInputMethod::sendKeyboardRectangleChanged); |
159 | connect(qGuiApp->inputMethod(), signal: &QInputMethod::inputDirectionChanged, context: this, slot: &QWaylandQtTextInputMethod::sendInputDirectionChanged); |
160 | connect(qGuiApp->inputMethod(), signal: &QInputMethod::localeChanged, context: this, slot: &QWaylandQtTextInputMethod::sendLocaleChanged); |
161 | } |
162 | |
163 | |
164 | QWaylandQtTextInputMethod::~QWaylandQtTextInputMethod() |
165 | { |
166 | } |
167 | |
168 | void QWaylandQtTextInputMethod::focusSurfaceDestroyed() |
169 | { |
170 | Q_D(QWaylandQtTextInputMethod); |
171 | d->focusDestroyListener.reset(); |
172 | d->waitingForSync = false; |
173 | d->resource = nullptr; |
174 | d->focusedSurface = nullptr; |
175 | } |
176 | |
177 | QWaylandSurface *QWaylandQtTextInputMethod::focusedSurface() const |
178 | { |
179 | Q_D(const QWaylandQtTextInputMethod); |
180 | return d->focusedSurface; |
181 | } |
182 | |
183 | QVariant QWaylandQtTextInputMethod::inputMethodQuery(Qt::InputMethodQuery property, QVariant argument) const |
184 | { |
185 | Q_D(const QWaylandQtTextInputMethod); |
186 | switch (property) { |
187 | case Qt::ImHints: |
188 | return int(d->hints); |
189 | case Qt::ImCursorRectangle: |
190 | return d->cursorRectangle; |
191 | case Qt::ImCursorPosition: |
192 | return d->cursorPosition; |
193 | case Qt::ImSurroundingText: |
194 | return d->surroundingText; |
195 | case Qt::ImAbsolutePosition: |
196 | return d->absolutePosition; |
197 | case Qt::ImCurrentSelection: |
198 | return d->surroundingText.mid(position: qMin(a: d->cursorPosition, b: d->anchorPosition), |
199 | n: qAbs(t: d->anchorPosition - d->cursorPosition)); |
200 | case Qt::ImAnchorPosition: |
201 | return d->anchorPosition; |
202 | case Qt::ImTextAfterCursor: |
203 | if (argument.isValid()) |
204 | return d->surroundingText.mid(position: d->cursorPosition, n: argument.toInt()); |
205 | return d->surroundingText.mid(position: d->cursorPosition); |
206 | case Qt::ImTextBeforeCursor: |
207 | if (argument.isValid()) |
208 | return d->surroundingText.left(n: d->cursorPosition).right(n: argument.toInt()); |
209 | return d->surroundingText.left(n: d->cursorPosition); |
210 | case Qt::ImPreferredLanguage: |
211 | return d->preferredLanguage; |
212 | |
213 | default: |
214 | return QVariant(); |
215 | } |
216 | } |
217 | |
218 | void QWaylandQtTextInputMethod::sendKeyEvent(QKeyEvent *event) |
219 | { |
220 | Q_D(QWaylandQtTextInputMethod); |
221 | if (d->resource == nullptr || d->resource->handle == nullptr) |
222 | return; |
223 | |
224 | d->send_key(d->resource->handle, |
225 | int(event->type()), |
226 | event->key(), |
227 | event->modifiers(), |
228 | event->isAutoRepeat(), |
229 | event->count(), |
230 | event->nativeScanCode(), |
231 | event->nativeVirtualKey(), |
232 | event->nativeModifiers(), |
233 | event->text()); |
234 | } |
235 | |
236 | void QWaylandQtTextInputMethod::sendInputMethodEvent(QInputMethodEvent *event) |
237 | { |
238 | Q_D(QWaylandQtTextInputMethod); |
239 | if (d->resource == nullptr || d->resource->handle == nullptr || d->compositor == nullptr) |
240 | return; |
241 | |
242 | if (d->updatingQueries != 0) { |
243 | qCWarning(qLcWaylandCompositorInputMethods) << "Input method event sent while client is updating. Ignored." ; |
244 | return; |
245 | } |
246 | |
247 | Q_ASSERT(!d->waitingForSync); |
248 | |
249 | QString oldSurroundText = d->surroundingText; |
250 | int oldCursorPosition = d->cursorPosition; |
251 | int oldAnchorPosition = d->anchorPosition; |
252 | int oldAbsolutePosition = d->absolutePosition; |
253 | QRect oldCursorRectangle = d->cursorRectangle; |
254 | QString oldPreferredLanguage = d->preferredLanguage; |
255 | Qt::InputMethodHints oldHints = d->hints; |
256 | |
257 | uint serial = d->compositor->nextSerial(); // ### Not needed if we block on this? |
258 | d->send_start_input_method_event(d->resource->handle, serial, d->surroundingTextOffset); |
259 | for (const QInputMethodEvent::Attribute &attribute : event->attributes()) { |
260 | switch (attribute.type) { |
261 | case QInputMethodEvent::TextFormat: |
262 | { |
263 | auto properties = attribute.value.value<QTextFormat>().properties(); |
264 | if (properties.size() != 2 || properties.firstKey() != QTextFormat::FontUnderline || properties.lastKey() != QTextFormat::TextUnderlineStyle) { |
265 | qCWarning(qLcWaylandCompositorInputMethods()) << "Only underline text formats currently supported" ; |
266 | } |
267 | |
268 | d->send_input_method_event_attribute(d->resource->handle, |
269 | serial, |
270 | attribute.type, |
271 | attribute.start, |
272 | attribute.length, |
273 | QString()); |
274 | break; |
275 | } |
276 | case QInputMethodEvent::Cursor: |
277 | d->cursorPosition = attribute.start; |
278 | d->send_input_method_event_attribute(d->resource->handle, |
279 | serial, |
280 | attribute.type, |
281 | attribute.start, |
282 | attribute.length, |
283 | attribute.value.typeId() == QMetaType::QColor ? attribute.value.value<QColor>().name() : QString()); |
284 | break; |
285 | case QInputMethodEvent::Language: // ### What is the type of value? Is it string? |
286 | Q_FALLTHROUGH(); |
287 | case QInputMethodEvent::Ruby: |
288 | d->send_input_method_event_attribute(d->resource->handle, |
289 | serial, |
290 | attribute.type, |
291 | attribute.start, |
292 | attribute.length, |
293 | attribute.value.toString()); |
294 | break; |
295 | case QInputMethodEvent::Selection: |
296 | d->send_input_method_event_attribute(d->resource->handle, |
297 | serial, |
298 | attribute.type, |
299 | attribute.start, |
300 | attribute.length, |
301 | QString()); |
302 | break; |
303 | } |
304 | } |
305 | |
306 | d->waitingForSync = true; |
307 | d->send_end_input_method_event(d->resource->handle, |
308 | serial, |
309 | event->commitString(), |
310 | event->preeditString(), |
311 | event->replacementStart(), |
312 | event->replacementLength()); |
313 | |
314 | while (d->waitingForSync) |
315 | d->compositor->processWaylandEvents(); |
316 | |
317 | Qt::InputMethodQueries queries; |
318 | if (d->surroundingText != oldSurroundText) |
319 | queries |= Qt::ImSurroundingText; |
320 | if (d->cursorPosition != oldCursorPosition) |
321 | queries |= Qt::ImCursorPosition; |
322 | if (d->anchorPosition != oldAnchorPosition) |
323 | queries |= Qt::ImAnchorPosition; |
324 | if (d->absolutePosition != oldAbsolutePosition) |
325 | queries |= Qt::ImAbsolutePosition; |
326 | if (d->cursorRectangle != oldCursorRectangle) |
327 | queries |= Qt::ImCursorRectangle; |
328 | if (d->preferredLanguage != oldPreferredLanguage) |
329 | queries |= Qt::ImPreferredLanguage; |
330 | if (d->hints != oldHints) |
331 | queries |= Qt::ImHints; |
332 | if (queries != 0) |
333 | emit updateInputMethod(queries); |
334 | } |
335 | |
336 | bool QWaylandQtTextInputMethod::isSurfaceEnabled(QWaylandSurface *surface) const |
337 | { |
338 | Q_D(const QWaylandQtTextInputMethod); |
339 | return d->enabledSurfaces.values().contains(surface); |
340 | } |
341 | |
342 | void QWaylandQtTextInputMethod::setFocus(QWaylandSurface *surface) |
343 | { |
344 | Q_D(QWaylandQtTextInputMethod); |
345 | |
346 | QWaylandQtTextInputMethodPrivate::Resource *resource = surface != nullptr ? d->resourceMap().value(surface->waylandClient()) : nullptr; |
347 | if (d->resource == resource) |
348 | return; |
349 | |
350 | if (d->resource != nullptr && d->focusedSurface != nullptr) { |
351 | d->send_leave(d->resource->handle, d->focusedSurface->resource()); |
352 | d->focusDestroyListener.reset(); |
353 | } |
354 | |
355 | d->resource = resource; |
356 | d->focusedSurface = surface; |
357 | |
358 | if (d->resource != nullptr && d->focusedSurface != nullptr) { |
359 | d->surroundingText.clear(); |
360 | d->cursorPosition = 0; |
361 | d->anchorPosition = 0; |
362 | d->absolutePosition = 0; |
363 | d->cursorRectangle = QRect(); |
364 | d->preferredLanguage.clear(); |
365 | d->hints = Qt::InputMethodHints(); |
366 | d->send_enter(d->resource->handle, d->focusedSurface->resource()); |
367 | sendInputDirectionChanged(); |
368 | sendLocaleChanged(); |
369 | sendInputDirectionChanged(); |
370 | d->focusDestroyListener.listenForDestruction(resource: surface->resource()); |
371 | if (d->inputPanelVisible && d->enabledSurfaces.values().contains(surface)) |
372 | qGuiApp->inputMethod()->show(); |
373 | } |
374 | } |
375 | |
376 | void QWaylandQtTextInputMethod::sendLocaleChanged() |
377 | { |
378 | Q_D(QWaylandQtTextInputMethod); |
379 | if (d->resource == nullptr || d->resource->handle == nullptr) |
380 | return; |
381 | |
382 | d->send_locale_changed(d->resource->handle, qGuiApp->inputMethod()->locale().bcp47Name()); |
383 | } |
384 | |
385 | void QWaylandQtTextInputMethod::sendInputDirectionChanged() |
386 | { |
387 | Q_D(QWaylandQtTextInputMethod); |
388 | if (d->resource == nullptr || d->resource->handle == nullptr) |
389 | return; |
390 | |
391 | d->send_input_direction_changed(d->resource->handle, int(qGuiApp->inputMethod()->inputDirection())); |
392 | } |
393 | |
394 | void QWaylandQtTextInputMethod::sendKeyboardRectangleChanged() |
395 | { |
396 | Q_D(QWaylandQtTextInputMethod); |
397 | if (d->resource == nullptr || d->resource->handle == nullptr) |
398 | return; |
399 | |
400 | QRectF keyboardRectangle = qGuiApp->inputMethod()->keyboardRectangle(); |
401 | d->send_keyboard_rectangle_changed(d->resource->handle, |
402 | wl_fixed_from_double(keyboardRectangle.x()), |
403 | wl_fixed_from_double(keyboardRectangle.y()), |
404 | wl_fixed_from_double(keyboardRectangle.width()), |
405 | wl_fixed_from_double(keyboardRectangle.height())); |
406 | } |
407 | |
408 | void QWaylandQtTextInputMethod::sendVisibleChanged() |
409 | { |
410 | Q_D(QWaylandQtTextInputMethod); |
411 | if (d->resource == nullptr || d->resource->handle == nullptr) |
412 | return; |
413 | |
414 | d->send_visible_changed(d->resource->handle, int(qGuiApp->inputMethod()->isVisible())); |
415 | } |
416 | |
417 | void QWaylandQtTextInputMethod::add(::wl_client *client, uint32_t id, int version) |
418 | { |
419 | Q_D(QWaylandQtTextInputMethod); |
420 | d->add(client, id, version); |
421 | } |
422 | |
423 | const struct wl_interface *QWaylandQtTextInputMethod::interface() |
424 | { |
425 | return QWaylandQtTextInputMethodPrivate::interface(); |
426 | } |
427 | |
428 | QByteArray QWaylandQtTextInputMethod::interfaceName() |
429 | { |
430 | return QWaylandQtTextInputMethodPrivate::interfaceName(); |
431 | } |
432 | |
433 | QT_END_NAMESPACE |
434 | |
435 | #include "moc_qwaylandqttextinputmethod.cpp" |
436 | |