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