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 LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3// Qt-Security score:significant reason:default
4
6
8#include "qwaylandinputmethodeventbuilder_p.h"
9
10#include <QtCore/qloggingcategory.h>
11#include <QtGui/qguiapplication.h>
12#include <QtGui/private/qhighdpiscaling_p.h>
13#include <QtGui/qevent.h>
14#include <QtGui/qwindow.h>
15#include <QTextCharFormat>
16
18
19Q_LOGGING_CATEGORY(qLcQpaWaylandTextInput, "qt.qpa.wayland.textinput")
20
21namespace QtWaylandClient {
22
23QWaylandTextInputv3::QWaylandTextInputv3(QWaylandDisplay *display,
24 struct ::zwp_text_input_v3 *text_input)
25 : QtWayland::zwp_text_input_v3(text_input)
26{
27 Q_UNUSED(display)
28}
29
31{
32 destroy();
33}
34
35namespace {
36const Qt::InputMethodQueries supportedQueries3 = Qt::ImEnabled |
37 Qt::ImSurroundingText |
38 Qt::ImCursorPosition |
39 Qt::ImAnchorPosition |
40 Qt::ImHints |
41 Qt::ImCursorRectangle;
42}
43
45{
46}
47
49{
50}
51
52void QWaylandTextInputv3::zwp_text_input_v3_enter(struct ::wl_surface *surface)
53{
54 qCDebug(qLcQpaWaylandTextInput) << Q_FUNC_INFO << "Trying to enable surface" << surface << "with focusing surface" << m_surface;
55
56 if (m_surface == surface)
57 return; // already enabled
58
59 m_surface = surface;
60 m_pendingPreeditString.clear();
61 m_pendingCommitString.clear();
62 m_pendingDeleteBeforeText = 0;
63 m_pendingDeleteAfterText = 0;
64 m_surroundingText.clear();
65 m_cursor = 0;
66 m_cursorPos = 0;
67 m_anchorPos = 0;
68 m_contentHint = 0;
69 m_contentPurpose = 0;
70 m_cursorRect = QRect();
71
72 enable();
73 updateState(supportedQueries3, update_state_enter);
74}
75
76void QWaylandTextInputv3::zwp_text_input_v3_leave(struct ::wl_surface *surface)
77{
78 qCDebug(qLcQpaWaylandTextInput) << Q_FUNC_INFO;
79
80 if (!m_surface)
81 return; // Nothing to leave
82
83 if (m_surface != surface)
84 qCWarning(qLcQpaWaylandTextInput()) << Q_FUNC_INFO << "Got leave event for surface" << surface << "with focusing surface" << m_surface;
85
86 m_currentPreeditString.clear();
87 m_surface = nullptr;
88 disable();
90}
91
92void QWaylandTextInputv3::zwp_text_input_v3_preedit_string(const QString &text, int32_t cursorBegin, int32_t cursorEnd)
93{
94 qCDebug(qLcQpaWaylandTextInput) << Q_FUNC_INFO << text << cursorBegin << cursorEnd;
95 if (!m_surface) {
96 qCWarning(qLcQpaWaylandTextInput) << "Got preedit_string event without entering a surface";
97 return;
98 }
99
100 if (!QGuiApplication::focusObject())
101 return;
102
103 m_pendingPreeditString.text = text;
104 m_pendingPreeditString.cursorBegin = QWaylandInputMethodEventBuilder::indexFromWayland(text, cursorBegin);
105 m_pendingPreeditString.cursorEnd = QWaylandInputMethodEventBuilder::indexFromWayland(text, cursorEnd);
106}
107
109{
110 qCDebug(qLcQpaWaylandTextInput) << Q_FUNC_INFO << text;
111 if (!m_surface) {
112 qCWarning(qLcQpaWaylandTextInput) << "Got commit_string event without entering a surface";
113 return;
114 }
115
116 if (!QGuiApplication::focusObject())
117 return;
118
119 m_pendingCommitString = text;
120}
121
122void QWaylandTextInputv3::zwp_text_input_v3_delete_surrounding_text(uint32_t beforeText, uint32_t afterText)
123{
124 qCDebug(qLcQpaWaylandTextInput) << Q_FUNC_INFO << beforeText << afterText;
125 if (!m_surface) {
126 qCWarning(qLcQpaWaylandTextInput) << "Got delete_surrounding_text event without entering a surface";
127 return;
128 }
129
130 if (!QGuiApplication::focusObject())
131 return;
132
133 m_pendingDeleteBeforeText = beforeText;
134 m_pendingDeleteAfterText = afterText;
135}
136
138{
139 qCDebug(qLcQpaWaylandTextInput) << Q_FUNC_INFO << "with serial" << serial << m_currentSerial;
140
141 if (!m_surface)
142 return;
143
144 // This is a case of double click.
145 // text_input_v3 will ignore this done signal and just keep the selection of the clicked word.
146 if (m_cursorPos != m_anchorPos && (m_pendingDeleteBeforeText != 0 || m_pendingDeleteAfterText != 0)) {
147 qCDebug(qLcQpaWaylandTextInput) << Q_FUNC_INFO << "Ignore done";
148 m_pendingDeleteBeforeText = 0;
149 m_pendingDeleteAfterText = 0;
150 m_pendingPreeditString.clear();
151 m_pendingCommitString.clear();
152 return;
153 }
154
155 QObject *focusObject = QGuiApplication::focusObject();
156 if (!focusObject)
157 return;
158
159 if (!m_surface) {
160 qCWarning(qLcQpaWaylandTextInput) << Q_FUNC_INFO << serial << "Surface is not enabled yet";
161 return;
162 }
163
164 if ((m_pendingPreeditString == m_currentPreeditString)
165 && (m_pendingCommitString.isEmpty() && m_pendingDeleteBeforeText == 0
166 && m_pendingDeleteAfterText == 0)) {
167 // Current done doesn't need additional updates
168 m_pendingPreeditString.clear();
169 return;
170 }
171
172 qCDebug(qLcQpaWaylandTextInput) << Q_FUNC_INFO << "PREEDIT" << m_pendingPreeditString.text << m_pendingPreeditString.cursorBegin;
173
174 QList<QInputMethodEvent::Attribute> attributes;
175 {
176 if (m_pendingPreeditString.cursorBegin != -1 ||
177 m_pendingPreeditString.cursorEnd != -1) {
178 // Current supported cursor shape is just line.
179 // It means, cursorEnd and cursorBegin are the same.
180 QInputMethodEvent::Attribute attribute1(QInputMethodEvent::Cursor,
181 m_pendingPreeditString.cursorBegin,
182 1);
183 attributes.append(attribute1);
184 }
185
186 // only use single underline style for now
187 QTextCharFormat format;
188 format.setFontUnderline(true);
189 format.setUnderlineStyle(QTextCharFormat::SingleUnderline);
190 QInputMethodEvent::Attribute attribute2(QInputMethodEvent::TextFormat,
191 0,
192 m_pendingPreeditString.text.length(), format);
193 attributes.append(attribute2);
194 }
195 QInputMethodEvent event(m_pendingPreeditString.text, attributes);
196
197 qCDebug(qLcQpaWaylandTextInput) << Q_FUNC_INFO << "DELETE" << m_pendingDeleteBeforeText << m_pendingDeleteAfterText;
198 qCDebug(qLcQpaWaylandTextInput) << Q_FUNC_INFO << "COMMIT" << m_pendingCommitString;
199
200 int replaceFrom = 0;
201 int replaceLength = 0;
202 if (m_pendingDeleteBeforeText != 0 || m_pendingDeleteAfterText != 0) {
203 // A workaround for reselection
204 // It will disable redundant commit after reselection
205 m_condReselection = true;
206 const QByteArray &utf8 = QStringView{m_surroundingText}.toUtf8();
207 if (m_cursorPos < int(m_pendingDeleteBeforeText)) {
208 replaceFrom = -QString::fromUtf8(QByteArrayView{utf8}.first(m_pendingDeleteBeforeText)).size();
209 replaceLength = QString::fromUtf8(QByteArrayView{utf8}.first(m_pendingDeleteBeforeText + m_pendingDeleteAfterText)).size();
210 } else {
211 replaceFrom = -QString::fromUtf8(QByteArrayView{utf8}.sliced(m_cursorPos - m_pendingDeleteBeforeText, m_pendingDeleteBeforeText)).size();
212 replaceLength = QString::fromUtf8(QByteArrayView{utf8}.sliced(m_cursorPos - m_pendingDeleteBeforeText, m_pendingDeleteBeforeText + m_pendingDeleteAfterText)).size();
213 }
214 }
215
216 qCDebug(qLcQpaWaylandTextInput) << Q_FUNC_INFO << "DELETE from " << replaceFrom << " length " << replaceLength;
217 event.setCommitString(m_pendingCommitString,
218 replaceFrom,
219 replaceLength);
220 m_currentPreeditString = m_pendingPreeditString;
221 m_pendingPreeditString.clear();
222 m_pendingCommitString.clear();
223 m_pendingDeleteBeforeText = 0;
224 m_pendingDeleteAfterText = 0;
225 QCoreApplication::sendEvent(focusObject, &event);
226
227 if (serial == m_currentSerial)
228 updateState(supportedQueries3, update_state_full);
229}
230
232{
233 qCDebug(qLcQpaWaylandTextInput) << Q_FUNC_INFO;
234
235 m_pendingPreeditString.clear();
236}
237
239{
240 m_currentSerial = (m_currentSerial < UINT_MAX) ? m_currentSerial + 1U: 0U;
241
242 qCDebug(qLcQpaWaylandTextInput) << Q_FUNC_INFO << "with serial" << m_currentSerial;
243 QtWayland::zwp_text_input_v3::commit();
244}
245
246void QWaylandTextInputv3::updateState(Qt::InputMethodQueries queries, uint32_t flags)
247{
248 qCDebug(qLcQpaWaylandTextInput) << Q_FUNC_INFO << queries << flags;
249
250 if (!QGuiApplication::focusObject())
251 return;
252
253 if (!QGuiApplication::focusWindow() || !QGuiApplication::focusWindow()->handle())
254 return;
255
256 auto *window = static_cast<QWaylandWindow *>(QGuiApplication::focusWindow()->handle());
257 auto *surface = window->wlSurface();
258 if (!surface || (surface != m_surface))
259 return;
260
261 queries &= supportedQueries3;
262 bool needsCommit = false;
263
264 QInputMethodQueryEvent event(queries);
265 QCoreApplication::sendEvent(QGuiApplication::focusObject(), &event);
266
267 // For some reason, a query for Qt::ImSurroundingText gives an empty string even though it is not.
268 if (!(queries & Qt::ImSurroundingText) && event.value(Qt::ImSurroundingText).toString().isEmpty()) {
269 return;
270 }
271
272 if (queries & Qt::ImCursorRectangle) {
273 const QRect &cRect = event.value(Qt::ImCursorRectangle).toRect();
274 const QRect &windowRect = QGuiApplication::inputMethod()->inputItemTransform().mapRect(cRect);
275 const QRect &nativeRect = QHighDpi::toNativePixels(windowRect, QGuiApplication::focusWindow());
276 const QMargins margins = window->clientSideMargins();
277 const QRect &surfaceRect = nativeRect.translated(margins.left(), margins.top());
278 if (surfaceRect != m_cursorRect) {
279 set_cursor_rectangle(surfaceRect.x(), surfaceRect.y(), surfaceRect.width(), surfaceRect.height());
280 m_cursorRect = surfaceRect;
281 needsCommit = true;
282 }
283 }
284
285 if ((queries & Qt::ImSurroundingText) || (queries & Qt::ImCursorPosition) || (queries & Qt::ImAnchorPosition)) {
286 QString text = event.value(Qt::ImSurroundingText).toString();
287 int cursor = event.value(Qt::ImCursorPosition).toInt();
288 int anchor = event.value(Qt::ImAnchorPosition).toInt();
289
290 qCDebug(qLcQpaWaylandTextInput) << "Original surrounding_text from InputMethodQuery: " << text << cursor << anchor;
291
292 // Make sure text is not too big
293 // surround_text cannot exceed 4000byte in wayland protocol
294 // The worst case will be supposed here.
295 const int MAX_MESSAGE_SIZE = 4000;
296
297 const QByteArray utf8 = text.toUtf8();
298 const int textSize = utf8.size();
299 if (textSize > MAX_MESSAGE_SIZE) {
300 qCDebug(qLcQpaWaylandTextInput) << "SurroundText size is over "
301 << MAX_MESSAGE_SIZE
302 << " byte, some text will be clipped.";
303 const int selectionStart = qMin(cursor, anchor);
304 const int selectionEnd = qMax(cursor, anchor);
305 const int selectionLength = selectionEnd - selectionStart;
306 QByteArray selection = QStringView{text}.sliced(selectionStart, selectionLength).toUtf8();
307 const int selectionSize = selection.size();
308 // If selection is bigger than 4000 byte, it is fixed to 4000 byte.
309 // anchor will be moved in the 4000 byte boundary.
310 if (selectionSize > MAX_MESSAGE_SIZE) {
311 if (anchor > cursor) {
312 cursor = 0;
313 anchor = MAX_MESSAGE_SIZE;
314 text = QString::fromUtf8(QByteArrayView{selection}.sliced(0, MAX_MESSAGE_SIZE));
315 } else {
316 anchor = 0;
317 cursor = MAX_MESSAGE_SIZE;
318 text = QString::fromUtf8(QByteArrayView{selection}.sliced(selectionSize - MAX_MESSAGE_SIZE, MAX_MESSAGE_SIZE));
319 }
320 } else {
321 // This is not optimal in some cases.
322 // For examples, if the cursor position and
323 // the selectionEnd are close to the end of the surround text,
324 // the tail of the text might always be clipped.
325 // However all the cases of over 4000 byte are just exceptions.
326 int selEndSize = QStringView{text}.first(selectionEnd).toUtf8().size();
327 cursor = QWaylandInputMethodEventBuilder::indexToWayland(text, cursor);
328 anchor = QWaylandInputMethodEventBuilder::indexToWayland(text, anchor);
329 if (selEndSize < MAX_MESSAGE_SIZE) {
330 text = QString::fromUtf8(QByteArrayView{utf8}.first(MAX_MESSAGE_SIZE));
331 } else {
332 const int startOffset = selEndSize - MAX_MESSAGE_SIZE;
333 text = QString::fromUtf8(QByteArrayView{utf8}.sliced(startOffset, MAX_MESSAGE_SIZE));
334 cursor -= startOffset;
335 anchor -= startOffset;
336 }
337 }
338 } else {
339 cursor = QWaylandInputMethodEventBuilder::indexToWayland(text, cursor);
340 anchor = QWaylandInputMethodEventBuilder::indexToWayland(text, anchor);
341 }
342 qCDebug(qLcQpaWaylandTextInput) << "Modified surrounding_text: " << text << cursor << anchor;
343
344 if (m_surroundingText != text || m_cursorPos != cursor || m_anchorPos != anchor) {
345 qCDebug(qLcQpaWaylandTextInput) << "Current surrounding_text: " << m_surroundingText << m_cursorPos << m_anchorPos;
346 qCDebug(qLcQpaWaylandTextInput) << "New surrounding_text: " << text << cursor << anchor;
347
348 set_surrounding_text(text, cursor, anchor);
349
350 // A workaround in the case of reselection
351 // It will work when re-clicking a preedit text
352 if (m_condReselection) {
353 qCDebug(qLcQpaWaylandTextInput) << "\"commit\" is disabled when Reselection by changing focus";
354 m_condReselection = false;
355 needsCommit = false;
356
357 }
358
359 m_surroundingText = text;
360 m_cursorPos = cursor;
361 m_anchorPos = anchor;
362 m_cursor = cursor;
363 }
364 }
365
366 if (queries & Qt::ImHints) {
367 QWaylandInputMethodContentType contentType = QWaylandInputMethodContentType::convertV3(static_cast<Qt::InputMethodHints>(event.value(Qt::ImHints).toInt()));
368 qCDebug(qLcQpaWaylandTextInput) << m_contentHint << contentType.hint;
369 qCDebug(qLcQpaWaylandTextInput) << m_contentPurpose << contentType.purpose;
370
371 if (m_contentHint != contentType.hint || m_contentPurpose != contentType.purpose) {
372 qCDebug(qLcQpaWaylandTextInput) << "set_content_type: " << contentType.hint << contentType.purpose;
373 set_content_type(contentType.hint, contentType.purpose);
374
375 m_contentHint = contentType.hint;
376 m_contentPurpose = contentType.purpose;
377 needsCommit = true;
378 }
379 }
380
381 if (needsCommit)
382 commit();
383}
384
386{
387 Q_UNUSED(cursor);
388}
389
391{
392 return false;
393}
394
396{
397 qCDebug(qLcQpaWaylandTextInput) << Q_FUNC_INFO;
398 return m_cursorRect;
399}
400
402{
403 return QLocale();
404}
405
407{
408 return Qt::LeftToRight;
409}
410
411}
412
413QT_END_NAMESPACE
void disableSurface(::wl_surface *) override
void zwp_text_input_v3_commit_string(const QString &text) override
Qt::LayoutDirection inputDirection() const override
void updateState(Qt::InputMethodQueries queries, uint32_t flags) override
void zwp_text_input_v3_enter(struct ::wl_surface *surface) override
void zwp_text_input_v3_preedit_string(const QString &text, int32_t cursor_begin, int32_t cursor_end) override
void zwp_text_input_v3_delete_surrounding_text(uint32_t before_length, uint32_t after_length) override
void zwp_text_input_v3_leave(struct ::wl_surface *surface) override
void enableSurface(::wl_surface *) override
void setCursorInsidePreedit(int cursor) override
void zwp_text_input_v3_done(uint32_t serial) override
Combined button and popup list for selecting options.