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
16QT_BEGIN_NAMESPACE
17
18QWaylandQtTextInputMethodPrivate::QWaylandQtTextInputMethodPrivate(QWaylandCompositor *c)
19 : compositor(c)
20{
21}
22
23void 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
35void 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
49void QWaylandQtTextInputMethodPrivate::text_input_method_v1_destroy(Resource *resource)
50{
51 if (this->resource == resource)
52 wl_resource_destroy(resource->handle);
53}
54
55void QWaylandQtTextInputMethodPrivate::text_input_method_v1_reset(Resource *resource)
56{
57 if (this->resource == resource)
58 qApp->inputMethod()->reset();
59}
60
61void QWaylandQtTextInputMethodPrivate::text_input_method_v1_commit(Resource *resource)
62{
63 if (this->resource == resource)
64 qApp->inputMethod()->commit();
65}
66
67void QWaylandQtTextInputMethodPrivate::text_input_method_v1_show_input_panel(Resource *resource)
68{
69 if (this->resource == resource)
70 qApp->inputMethod()->show();
71}
72
73void QWaylandQtTextInputMethodPrivate::text_input_method_v1_hide_input_panel(Resource *resource)
74{
75 if (this->resource == resource)
76 qApp->inputMethod()->hide();
77}
78
79void 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
85void 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
91void 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
97void 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
103void 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
109void 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
115void 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
123void 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
129void 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
135void 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
145void QWaylandQtTextInputMethodPrivate::text_input_method_v1_acknowledge_input_method(Resource *resource)
146{
147 if (this->resource == resource)
148 waitingForSync = false;
149}
150
151QWaylandQtTextInputMethod::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
164QWaylandQtTextInputMethod::~QWaylandQtTextInputMethod()
165{
166}
167
168void 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
177QWaylandSurface *QWaylandQtTextInputMethod::focusedSurface() const
178{
179 Q_D(const QWaylandQtTextInputMethod);
180 return d->focusedSurface;
181}
182
183QVariant 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
218void 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
236void 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
336bool QWaylandQtTextInputMethod::isSurfaceEnabled(QWaylandSurface *surface) const
337{
338 Q_D(const QWaylandQtTextInputMethod);
339 return d->enabledSurfaces.values().contains(surface);
340}
341
342void 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
376void 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
385void 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
394void 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
408void 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
417void QWaylandQtTextInputMethod::add(::wl_client *client, uint32_t id, int version)
418{
419 Q_D(QWaylandQtTextInputMethod);
420 d->add(client, id, version);
421}
422
423const struct wl_interface *QWaylandQtTextInputMethod::interface()
424{
425 return QWaylandQtTextInputMethodPrivate::interface();
426}
427
428QByteArray QWaylandQtTextInputMethod::interfaceName()
429{
430 return QWaylandQtTextInputMethodPrivate::interfaceName();
431}
432
433QT_END_NAMESPACE
434
435#include "moc_qwaylandqttextinputmethod.cpp"
436

source code of qtwayland/src/compositor/extensions/qwaylandqttextinputmethod.cpp