Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qwaylandtextinputv3.cpp
Go to the documentation of this file.
1// Copyright (C) 2021 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3// Qt-Security score:critical reason:network-protocol
4
7
8#include <QtWaylandCompositor/QWaylandCompositor>
9#include <QtWaylandCompositor/private/qwaylandseat_p.h>
10
12#include "qwaylandview.h"
13#include "qwaylandinputmethodeventbuilder_p.h"
14
15#include <QGuiApplication>
16#include <QInputMethodEvent>
17#include <qpa/qwindowsysteminterface.h>
18
19#if QT_CONFIG(xkbcommon)
20#include <QtGui/private/qxkbcommon_p.h>
21#endif
22
24
25QWaylandTextInputV3ClientState::QWaylandTextInputV3ClientState()
26{
27}
28
30{
31 qCDebug(qLcWaylandCompositorTextInput) << Q_FUNC_INFO;
32
33 Qt::InputMethodQueries queries;
34
35 if (hints != other.hints)
36 queries |= Qt::ImHints;
37 if (cursorRectangle != other.cursorRectangle)
38 queries |= Qt::ImCursorRectangle;
39 if (surroundingText != other.surroundingText)
40 queries |= Qt::ImSurroundingText | Qt::ImCurrentSelection;
41 if (cursorPosition != other.cursorPosition)
42 queries |= Qt::ImCursorPosition | Qt::ImCurrentSelection;
43 if (anchorPosition != other.anchorPosition)
44 queries |= Qt::ImAnchorPosition | Qt::ImCurrentSelection;
45
46 return queries;
47}
48
50
51 Qt::InputMethodQueries queries;
52
53 if ((other.changedState & Qt::ImHints) && hints != other.hints) {
54 hints = other.hints;
55 queries |= Qt::ImHints;
56 }
57
58 if ((other.changedState & Qt::ImCursorRectangle) && cursorRectangle != other.cursorRectangle) {
59 cursorRectangle = other.cursorRectangle;
60 queries |= Qt::ImCursorRectangle;
61 }
62
63 if ((other.changedState & Qt::ImSurroundingText) && surroundingText != other.surroundingText) {
64 surroundingText = other.surroundingText;
65 queries |= Qt::ImSurroundingText | Qt::ImCurrentSelection;
66 }
67
68 if ((other.changedState & Qt::ImCursorPosition) && cursorPosition != other.cursorPosition) {
70 queries |= Qt::ImCursorPosition | Qt::ImCurrentSelection;
71 }
72
73 if ((other.changedState & Qt::ImAnchorPosition) && anchorPosition != other.anchorPosition) {
75 queries |= Qt::ImAnchorPosition | Qt::ImCurrentSelection;
76 }
77
78 return queries;
79}
80
81QWaylandTextInputV3Private::QWaylandTextInputV3Private(QWaylandCompositor *compositor)
82 : compositor(compositor)
83 , currentState(new QWaylandTextInputV3ClientState)
84 , pendingState(new QWaylandTextInputV3ClientState)
85{
86}
87
88void QWaylandTextInputV3Private::sendInputMethodEvent(QInputMethodEvent *event)
89{
90 qCDebug(qLcWaylandCompositorTextInput) << Q_FUNC_INFO;
91
92 if (!focusResource || !focusResource->handle)
93 return;
94
95 bool needsDone = false;
96
97 const QString &newPreeditString = event->preeditString();
98
99 // Current cursor shape is only line. It means both cursorBegin
100 // and cursorEnd will be the same values.
101 int32_t preeditCursorPos = newPreeditString.toUtf8().size();
102
103 if (event->replacementLength() > 0) {
104 int replacementStart = event->replacementStart();
105 int replacementLength = event->replacementLength();
106 const int cursorPos = currentState->cursorPosition;
107 if (currentState->cursorPosition < -event->replacementStart()) {
108 qCWarning(qLcWaylandCompositorTextInput)
109 << Q_FUNC_INFO
110 << "Invalid replacementStart :" << replacementStart
111 << "on the cursorPosition :" << cursorPos;
112 replacementStart = -cursorPos;
113 }
114 auto targetText = QStringView{currentState->surroundingText}.sliced(cursorPos + replacementStart);
115 if (targetText.length() < replacementLength) {
116 qCWarning(qLcWaylandCompositorTextInput)
117 << Q_FUNC_INFO
118 << "Invalid replacementLength :" << replacementLength
119 << "for the surrounding text :" << targetText;
120 replacementLength = targetText.length();
121 }
122 const int before = targetText.first(-replacementStart).toUtf8().size();
123 const int after = targetText.first(replacementLength).toUtf8().size() - before;
124
125 send_delete_surrounding_text(focusResource->handle, before, after);
126 needsDone = true;
127
128 // The commit will also be applied here
129 currentState->surroundingText.replace(cursorPos + replacementStart,
130 replacementLength,
131 event->commitString());
132 currentState->cursorPosition = cursorPos + replacementStart + event->commitString().length();
133 currentState->anchorPosition = cursorPos + replacementStart + event->commitString().length();
134 qApp->inputMethod()->update(Qt::ImSurroundingText | Qt::ImCursorPosition | Qt::ImAnchorPosition);
135 }
136
137 if (currentPreeditString != newPreeditString) {
138 currentPreeditString = newPreeditString;
139 send_preedit_string(focusResource->handle, currentPreeditString, preeditCursorPos, preeditCursorPos);
140 needsDone = true;
141 }
142 if (!event->commitString().isEmpty()) {
143 send_commit_string(focusResource->handle, event->commitString());
144 needsDone = true;
145 }
146
147 if (needsDone)
148 send_done(focusResource->handle, serials[focusResource]);
149}
150
151
152void QWaylandTextInputV3Private::sendKeyEvent(QKeyEvent *event)
153{
154 qCDebug(qLcWaylandCompositorTextInput) << Q_FUNC_INFO;
155
156 if (!focusResource || !focusResource->handle)
157 return;
158
159 send_commit_string(focusResource->handle, event->text());
160
161 send_done(focusResource->handle, serials[focusResource]);
162}
163
164QVariant QWaylandTextInputV3Private::inputMethodQuery(Qt::InputMethodQuery property, QVariant argument) const
165{
166 qCDebug(qLcWaylandCompositorTextInput) << Q_FUNC_INFO << property;
167
168 switch (property) {
169 case Qt::ImHints:
170 return QVariant(static_cast<int>(currentState->hints));
171 case Qt::ImCursorRectangle:
172 return currentState->cursorRectangle;
173 case Qt::ImFont:
174 // Not supported
175 return QVariant();
176 case Qt::ImCursorPosition:
177 qCDebug(qLcWaylandCompositorTextInput) << currentState->cursorPosition;
178 return currentState->cursorPosition;
179 case Qt::ImSurroundingText:
180 qCDebug(qLcWaylandCompositorTextInput) << currentState->surroundingText;
181 return currentState->surroundingText;
182 case Qt::ImCurrentSelection:
183 return currentState->surroundingText.mid(qMin(currentState->cursorPosition, currentState->anchorPosition),
184 qAbs(currentState->anchorPosition - currentState->cursorPosition));
185 case Qt::ImMaximumTextLength:
186 // Not supported
187 return QVariant();
188 case Qt::ImAnchorPosition:
189 qCDebug(qLcWaylandCompositorTextInput) << currentState->anchorPosition;
190 return currentState->anchorPosition;
191 case Qt::ImAbsolutePosition:
192 // We assume the surrounding text is our whole document for now
193 return currentState->cursorPosition;
194 case Qt::ImTextAfterCursor:
195 if (argument.isValid())
196 return currentState->surroundingText.mid(currentState->cursorPosition, argument.toInt());
197 return currentState->surroundingText.mid(currentState->cursorPosition);
198 case Qt::ImTextBeforeCursor:
199 if (argument.isValid())
200 return currentState->surroundingText.left(currentState->cursorPosition).right(argument.toInt());
201 return currentState->surroundingText.left(currentState->cursorPosition);
202
203 default:
204 return QVariant();
205 }
206}
207
208void QWaylandTextInputV3Private::setFocus(QWaylandSurface *surface)
209{
210 qCDebug(qLcWaylandCompositorTextInput) << Q_FUNC_INFO << surface;
211
212 if (focusResource && focus) {
213 qApp->inputMethod()->commit();
214 qApp->inputMethod()->hide();
215 inputPanelVisible = false;
216 send_leave(focusResource->handle, focus->resource());
217 currentPreeditString.clear();
218 }
219
220 if (focus != surface)
221 focusDestroyListener.reset();
222
223 Resource *resource = surface ? resourceMap().value(surface->waylandClient()) : 0;
224 if (resource && surface) {
225 send_enter(resource->handle, surface->resource());
226
227 if (focus != surface)
228 focusDestroyListener.listenForDestruction(surface->resource());
229 }
230
231 focus = surface;
232 focusResource = resource;
233}
234
235void QWaylandTextInputV3Private::zwp_text_input_v3_bind_resource(Resource *resource)
236{
237 qCDebug(qLcWaylandCompositorTextInput) << Q_FUNC_INFO;
238
239 serials.insert(resource, 0);
240}
241
242void QWaylandTextInputV3Private::zwp_text_input_v3_destroy_resource(Resource *resource)
243{
244 qCDebug(qLcWaylandCompositorTextInput) << Q_FUNC_INFO;
245
246 serials.remove(resource);
247 if (focusResource == resource)
248 focusResource = nullptr;
249}
250
251void QWaylandTextInputV3Private::zwp_text_input_v3_destroy(Resource *resource)
252{
253 qCDebug(qLcWaylandCompositorTextInput) << Q_FUNC_INFO;
254
255 wl_resource_destroy(resource->handle);
256}
257
258void QWaylandTextInputV3Private::zwp_text_input_v3_enable(Resource *resource)
259{
260 qCDebug(qLcWaylandCompositorTextInput) << Q_FUNC_INFO;
261
262 Q_Q(QWaylandTextInputV3);
263
264 pendingState.reset(new QWaylandTextInputV3ClientState);
265
266 enabledSurfaces.insert(resource, focus);
267 emit q->surfaceEnabled(focus);
268
269 inputPanelVisible = true;
270 qApp->inputMethod()->show();
271}
272
273void QWaylandTextInputV3Private::zwp_text_input_v3_disable(QtWaylandServer::zwp_text_input_v3::Resource *resource)
274{
275 qCDebug(qLcWaylandCompositorTextInput) << Q_FUNC_INFO;
276
277 Q_Q(QWaylandTextInputV3);
278
279 QWaylandSurface *s = enabledSurfaces.take(resource);
280 emit q->surfaceDisabled(s);
281
282 // When reselecting a word by setFocus
283 qApp->inputMethod()->commit();
284
285 qApp->inputMethod()->reset();
286 pendingState.reset(new QWaylandTextInputV3ClientState);
287}
288
289void QWaylandTextInputV3Private::zwp_text_input_v3_set_cursor_rectangle(Resource *resource, int32_t x, int32_t y, int32_t width, int32_t height)
290{
291 qCDebug(qLcWaylandCompositorTextInput) << Q_FUNC_INFO << x << y << width << height;
292
293 if (resource != focusResource)
294 return;
295
296 pendingState->cursorRectangle = QRect(x, y, width, height);
297
298 pendingState->changedState |= Qt::ImCursorRectangle;
299}
300
301void QWaylandTextInputV3Private::zwp_text_input_v3_commit(Resource *resource)
302{
303 qCDebug(qLcWaylandCompositorTextInput) << Q_FUNC_INFO;
304
305 serials[resource] = serials[resource] < UINT_MAX ? serials[resource] + 1U : 0U;
306
307 // Just increase serials and ignore empty commits
308 if (!pendingState->changedState) {
309 qCDebug(qLcWaylandCompositorTextInput) << Q_FUNC_INFO << "pendingState is not changed";
310 return;
311 }
312
313 // Selection starts.
314 // But since qtvirtualkeyboard with hunspell does not reset its preedit string,
315 // compositor forces to reset inputMethod.
316 if ((currentState->cursorPosition == currentState->anchorPosition)
317 && (pendingState->cursorPosition != pendingState->anchorPosition))
318 qApp->inputMethod()->reset();
319
320 // Enable reselection
321 // This is a workaround to make qtvirtualkeyboad's state empty by clearing State::InputMethodClick.
322 if (currentState->surroundingText == pendingState->surroundingText && currentState->cursorPosition != pendingState->cursorPosition)
323 qApp->inputMethod()->invokeAction(QInputMethod::Click, pendingState->cursorPosition);
324
325 Qt::InputMethodQueries queries = currentState->mergeChanged(*pendingState.data());
326 pendingState.reset(new QWaylandTextInputV3ClientState);
327
328 if (queries) {
329 qCDebug(qLcWaylandCompositorTextInput) << "QInputMethod::update() after commit with" << queries;
330
331 qApp->inputMethod()->update(queries);
332 }
333}
334
335void QWaylandTextInputV3Private::zwp_text_input_v3_set_content_type(Resource *resource, uint32_t hint, uint32_t purpose)
336{
337 qCDebug(qLcWaylandCompositorTextInput) << Q_FUNC_INFO << hint << purpose;
338
339 if (resource != focusResource)
340 return;
341
342 pendingState->hints = Qt::ImhNone;
343
344 if ((hint & content_hint_completion) == 0)
345 pendingState->hints |= Qt::ImhNoPredictiveText;
346 if ((hint & content_hint_auto_capitalization) == 0)
347 pendingState->hints |= Qt::ImhNoAutoUppercase;
348 if ((hint & content_hint_lowercase) != 0)
349 pendingState->hints |= Qt::ImhPreferLowercase;
350 if ((hint & content_hint_uppercase) != 0)
351 pendingState->hints |= Qt::ImhPreferUppercase;
352 if ((hint & content_hint_hidden_text) != 0)
353 pendingState->hints |= Qt::ImhHiddenText;
354 if ((hint & content_hint_sensitive_data) != 0)
355 pendingState->hints |= Qt::ImhSensitiveData;
356 if ((hint & content_hint_latin) != 0)
357 pendingState->hints |= Qt::ImhLatinOnly;
358 if ((hint & content_hint_multiline) != 0)
359 pendingState->hints |= Qt::ImhMultiLine;
360
361 switch (purpose) {
362 case content_purpose_normal:
363 break;
364 case content_purpose_alpha:
365 pendingState->hints |= Qt::ImhUppercaseOnly | Qt::ImhLowercaseOnly;
366 break;
367 case content_purpose_digits:
368 pendingState->hints |= Qt::ImhDigitsOnly;
369 break;
370 case content_purpose_number:
371 pendingState->hints |= Qt::ImhFormattedNumbersOnly;
372 break;
373 case content_purpose_phone:
374 pendingState->hints |= Qt::ImhDialableCharactersOnly;
375 break;
376 case content_purpose_url:
377 pendingState->hints |= Qt::ImhUrlCharactersOnly;
378 break;
379 case content_purpose_email:
380 pendingState->hints |= Qt::ImhEmailCharactersOnly;
381 break;
382 case content_purpose_name:
383 case content_purpose_password:
384 break;
385 case content_purpose_date:
386 pendingState->hints |= Qt::ImhDate;
387 break;
388 case content_purpose_time:
389 pendingState->hints |= Qt::ImhTime;
390 break;
391 case content_purpose_datetime:
392 pendingState->hints |= Qt::ImhDate | Qt::ImhTime;
393 break;
394 case content_purpose_terminal:
395 default:
396 break;
397 }
398
399 qCDebug(qLcWaylandCompositorTextInput) << Q_FUNC_INFO << pendingState->hints;
400
401 pendingState->changedState |= Qt::ImHints;
402}
403
404void QWaylandTextInputV3Private::zwp_text_input_v3_set_surrounding_text(Resource *resource, const QString &text, int32_t cursor, int32_t anchor)
405{
406 qCDebug(qLcWaylandCompositorTextInput) << Q_FUNC_INFO << text << cursor << anchor;
407
408 if (resource != focusResource)
409 return;
410
411 pendingState->surroundingText = text;
412 pendingState->cursorPosition = QWaylandInputMethodEventBuilder::indexFromWayland(text, cursor);
413 pendingState->anchorPosition = QWaylandInputMethodEventBuilder::indexFromWayland(text, anchor);
414
415 pendingState->changedState |= Qt::ImSurroundingText | Qt::ImCursorPosition | Qt::ImAnchorPosition;
416}
417
418void QWaylandTextInputV3Private::zwp_text_input_v3_set_text_change_cause(Resource *resource, uint32_t cause)
419{
420 qCDebug(qLcWaylandCompositorTextInput) << Q_FUNC_INFO;
421
422 Q_UNUSED(resource);
423 Q_UNUSED(cause);
424}
425
426QWaylandTextInputV3::QWaylandTextInputV3(QWaylandObject *container, QWaylandCompositor *compositor)
427 : QWaylandCompositorExtensionTemplate(container, *new QWaylandTextInputV3Private(compositor))
428{
429 connect(&d_func()->focusDestroyListener, &QWaylandDestroyListener::fired,
430 this, &QWaylandTextInputV3::focusSurfaceDestroyed);
431}
432
436
437void QWaylandTextInputV3::sendInputMethodEvent(QInputMethodEvent *event)
438{
439 Q_D(QWaylandTextInputV3);
440
441 d->sendInputMethodEvent(event);
442}
443
444void QWaylandTextInputV3::sendKeyEvent(QKeyEvent *event)
445{
446 Q_D(QWaylandTextInputV3);
447
448 d->sendKeyEvent(event);
449}
450
451QVariant QWaylandTextInputV3::inputMethodQuery(Qt::InputMethodQuery property, QVariant argument) const
452{
453 const Q_D(QWaylandTextInputV3);
454
455 return d->inputMethodQuery(property, argument);
456}
457
458QWaylandSurface *QWaylandTextInputV3::focus() const
459{
460 const Q_D(QWaylandTextInputV3);
461
462 return d->focus;
463}
464
465void QWaylandTextInputV3::setFocus(QWaylandSurface *surface)
466{
467 Q_D(QWaylandTextInputV3);
468
469 d->setFocus(surface);
470}
471
472void QWaylandTextInputV3::focusSurfaceDestroyed(void *)
473{
474 qCDebug(qLcWaylandCompositorTextInput) << Q_FUNC_INFO;
475
476 Q_D(QWaylandTextInputV3);
477
478 d->focusDestroyListener.reset();
479
480 d->focus = nullptr;
481 d->focusResource = nullptr;
482}
483
484bool QWaylandTextInputV3::isSurfaceEnabled(QWaylandSurface *surface) const
485{
486 qCDebug(qLcWaylandCompositorTextInput) << Q_FUNC_INFO;
487
488 const Q_D(QWaylandTextInputV3);
489
490 return d->enabledSurfaces.values().contains(surface);
491}
492
493void QWaylandTextInputV3::add(::wl_client *client, uint32_t id, int version)
494{
495 qCDebug(qLcWaylandCompositorTextInput) << Q_FUNC_INFO << client << id << version;
496
497 Q_D(QWaylandTextInputV3);
498
499 d->add(client, id, version);
500}
501
502const wl_interface *QWaylandTextInputV3::interface()
503{
504 return QWaylandTextInputV3Private::interface();
505}
506
507QByteArray QWaylandTextInputV3::interfaceName()
508{
509 return QWaylandTextInputV3Private::interfaceName();
510}
511
512QT_END_NAMESPACE
Qt::InputMethodQueries mergeChanged(const QWaylandTextInputV3ClientState &other)
Qt::InputMethodQueries updatedQueries(const QWaylandTextInputV3ClientState &other) const
static const struct wl_interface * interface()
QVariant inputMethodQuery(Qt::InputMethodQuery property, QVariant argument) const
void sendInputMethodEvent(QInputMethodEvent *event)
void setFocus(QWaylandSurface *surface)
void add(::wl_client *client, uint32_t id, int version)
QWaylandSurface * focus() const
void sendKeyEvent(QKeyEvent *event)
bool isSurfaceEnabled(QWaylandSurface *surface) const
Combined button and popup list for selecting options.