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