Qt 6.x
The Qt SDK
Loading...
Searching...
No Matches
qwaylandtextinputv4.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
5
6#include "qwaylandwindow_p.h"
8
9#include <QtCore/qloggingcategory.h>
10#include <QtGui/qguiapplication.h>
11#include <QtGui/qevent.h>
12#include <QtGui/qwindow.h>
13#include <QTextCharFormat>
14
16
17Q_LOGGING_CATEGORY(qLcQpaWaylandTextInput, "qt.qpa.wayland.textinput")
18
19namespace QtWaylandClient {
20
21QWaylandTextInputv4::QWaylandTextInputv4(QWaylandDisplay *display,
22 struct ::zwp_text_input_v4 *text_input)
23 : QtWayland::zwp_text_input_v4(text_input)
24 , m_display(display)
25{
26
27}
28
30{
31}
32
33namespace {
34const Qt::InputMethodQueries supportedQueries4 = Qt::ImEnabled |
40}
41
42void QWaylandTextInputv4::zwp_text_input_v4_enter(struct ::wl_surface *surface)
43{
44 qCDebug(qLcQpaWaylandTextInput) << Q_FUNC_INFO;
45
46 m_surface = surface;
47
48 m_pendingPreeditString.clear();
49 m_pendingCommitString.clear();
50 m_pendingDeleteBeforeText = 0;
51 m_pendingDeleteAfterText = 0;
52
53 enable();
54 updateState(supportedQueries4, update_state_enter);
55}
56
57void QWaylandTextInputv4::zwp_text_input_v4_leave(struct ::wl_surface *surface)
58{
59 qCDebug(qLcQpaWaylandTextInput) << Q_FUNC_INFO;
60
61 if (m_surface != surface) {
62 qCWarning(qLcQpaWaylandTextInput()) << Q_FUNC_INFO << "Got leave event for surface" << surface << "focused surface" << m_surface;
63 return;
64 }
65
66 // QTBUG-97248: check commit_mode
67 // Currently text-input-unstable-v4-wip is implemented with preedit_commit_mode
68 // 'commit'
69
70 m_currentPreeditString.clear();
71
72 m_surface = nullptr;
73 m_currentSerial = 0U;
74
75 disable();
76 qCDebug(qLcQpaWaylandTextInput) << Q_FUNC_INFO << "Done";
77}
78
79void QWaylandTextInputv4::zwp_text_input_v4_preedit_string(const QString &text, int32_t cursorBegin, int32_t cursorEnd)
80{
81 qCDebug(qLcQpaWaylandTextInput) << Q_FUNC_INFO << text << cursorBegin << cursorEnd;
82
84 return;
85
86 m_pendingPreeditString.text = text;
87 m_pendingPreeditString.cursorBegin = cursorBegin;
88 m_pendingPreeditString.cursorEnd = cursorEnd;
89}
90
92{
93 qCDebug(qLcQpaWaylandTextInput) << Q_FUNC_INFO << text;
94
96 return;
97
98 m_pendingCommitString = text;
99}
100
101void QWaylandTextInputv4::zwp_text_input_v4_delete_surrounding_text(uint32_t beforeText, uint32_t afterText)
102{
103 qCDebug(qLcQpaWaylandTextInput) << Q_FUNC_INFO << beforeText << afterText;
104
106 return;
107
108 m_pendingDeleteBeforeText = QWaylandInputMethodEventBuilder::indexFromWayland(m_surroundingText, beforeText);
109 m_pendingDeleteAfterText = QWaylandInputMethodEventBuilder::indexFromWayland(m_surroundingText, afterText);
110}
111
113{
114 qCDebug(qLcQpaWaylandTextInput) << Q_FUNC_INFO << "with serial" << serial << m_currentSerial;
115
116 // This is a case of double click.
117 // text_input_v4 will ignore this done signal and just keep the selection of the clicked word.
118 if (m_cursorPos != m_anchorPos && (m_pendingDeleteBeforeText != 0 || m_pendingDeleteAfterText != 0)) {
119 qCDebug(qLcQpaWaylandTextInput) << Q_FUNC_INFO << "Ignore done";
120 m_pendingDeleteBeforeText = 0;
121 m_pendingDeleteAfterText = 0;
122 m_pendingPreeditString.clear();
123 m_pendingCommitString.clear();
124 return;
125 }
126
127 QObject *focusObject = QGuiApplication::focusObject();
128 if (!focusObject)
129 return;
130
131 if (!m_surface) {
132 qCWarning(qLcQpaWaylandTextInput) << Q_FUNC_INFO << serial << "Surface is not enabled yet";
133 return;
134 }
135
136 qCDebug(qLcQpaWaylandTextInput) << Q_FUNC_INFO << "PREEDIT" << m_pendingPreeditString.text << m_pendingPreeditString.cursorBegin;
137
139 {
140 if (m_pendingPreeditString.cursorBegin != -1 ||
141 m_pendingPreeditString.cursorEnd != -1) {
142 // Current supported cursor shape is just line.
143 // It means, cursorEnd and cursorBegin are the same.
145 m_pendingPreeditString.text.length(),
146 1);
147 attributes.append(attribute1);
148 }
149
150 // only use single underline style for now
152 format.setFontUnderline(true);
153 format.setUnderlineStyle(QTextCharFormat::SingleUnderline);
155 0,
156 m_pendingPreeditString.text.length(), format);
157 attributes.append(attribute2);
158 }
159 QInputMethodEvent event(m_pendingPreeditString.text, attributes);
160
161 qCDebug(qLcQpaWaylandTextInput) << Q_FUNC_INFO << "DELETE" << m_pendingDeleteBeforeText << m_pendingDeleteAfterText;
162 qCDebug(qLcQpaWaylandTextInput) << Q_FUNC_INFO << "COMMIT" << m_pendingCommitString;
163
164 // A workaround for reselection
165 // It will disable redundant commit after reselection
166 if (m_pendingDeleteBeforeText != 0 || m_pendingDeleteAfterText != 0)
167 m_condReselection = true;
168
169 event.setCommitString(m_pendingCommitString,
170 -m_pendingDeleteBeforeText,
171 m_pendingDeleteBeforeText + m_pendingDeleteAfterText);
172 m_currentPreeditString = m_pendingPreeditString;
173 m_pendingPreeditString.clear();
174 m_pendingCommitString.clear();
175 m_pendingDeleteBeforeText = 0;
176 m_pendingDeleteAfterText = 0;
177 QCoreApplication::sendEvent(focusObject, &event);
178
179 if (serial == m_currentSerial)
180 updateState(supportedQueries4, update_state_full);
181}
182
184{
185 qCDebug(qLcQpaWaylandTextInput) << Q_FUNC_INFO;
186
187 m_pendingPreeditString.clear();
188}
189
191{
192 qCDebug(qLcQpaWaylandTextInput) << Q_FUNC_INFO;
193}
194
195void QWaylandTextInputv4::disableSurface(::wl_surface *surface)
196{
197 qCDebug(qLcQpaWaylandTextInput) << Q_FUNC_INFO;
198
199 if (m_surface != surface) {
200 qCWarning(qLcQpaWaylandTextInput()) << Q_FUNC_INFO << "for surface" << surface << "focused surface" << m_surface;
201 return;
202 }
203}
204
206{
207 m_currentSerial = (m_currentSerial < UINT_MAX) ? m_currentSerial + 1U: 0U;
208
209 qCDebug(qLcQpaWaylandTextInput) << Q_FUNC_INFO << "with serial" << m_currentSerial;
210 QtWayland::zwp_text_input_v4::commit();
211}
212
213void QWaylandTextInputv4::updateState(Qt::InputMethodQueries queries, uint32_t flags)
214{
215 qCDebug(qLcQpaWaylandTextInput) << Q_FUNC_INFO << queries << flags;
216
218 return;
219
221 return;
222
223 auto *window = static_cast<QWaylandWindow *>(QGuiApplication::focusWindow()->handle());
224 auto *surface = window->wlSurface();
225 if (!surface || (surface != m_surface))
226 return;
227
228 queries &= supportedQueries4;
229 bool needsCommit = false;
230
233
234 // For some reason, a query for Qt::ImSurroundingText gives an empty string even though it is not.
235 if (!(queries & Qt::ImSurroundingText) && event.value(Qt::ImSurroundingText).toString().isEmpty()) {
236 return;
237 }
238
239 if (queries & Qt::ImCursorRectangle) {
240 const QRect &cRect = event.value(Qt::ImCursorRectangle).toRect();
241 const QRect &windowRect = QGuiApplication::inputMethod()->inputItemTransform().mapRect(cRect);
242 const QMargins margins = window->frameMargins();
243 const QRect &surfaceRect = windowRect.translated(margins.left(), margins.top());
244 if (surfaceRect != m_cursorRect) {
245 set_cursor_rectangle(surfaceRect.x(), surfaceRect.y(), surfaceRect.width(), surfaceRect.height());
246 m_cursorRect = surfaceRect;
247 needsCommit = true;
248 }
249 }
250
251 if ((queries & Qt::ImSurroundingText) || (queries & Qt::ImCursorPosition) || (queries & Qt::ImAnchorPosition)) {
252 QString text = event.value(Qt::ImSurroundingText).toString();
253 int cursor = event.value(Qt::ImCursorPosition).toInt();
254 int anchor = event.value(Qt::ImAnchorPosition).toInt();
255
256 qCDebug(qLcQpaWaylandTextInput) << "Orginal surrounding_text from InputMethodQuery: " << text << cursor << anchor;
257
258 // Make sure text is not too big
259 // surround_text cannot exceed 4000byte in wayland protocol
260 // The worst case will be supposed here.
261 const int MAX_MESSAGE_SIZE = 4000;
262
263 if (text.toUtf8().size() > MAX_MESSAGE_SIZE) {
264 const int selectionStart = QWaylandInputMethodEventBuilder::indexToWayland(text, qMin(cursor, anchor));
265 const int selectionEnd = QWaylandInputMethodEventBuilder::indexToWayland(text, qMax(cursor, anchor));
266 const int selectionLength = selectionEnd - selectionStart;
267 // If selection is bigger than 4000 byte, it is fixed to 4000 byte.
268 // anchor will be moved in the 4000 byte boundary.
269 if (selectionLength > MAX_MESSAGE_SIZE) {
270 if (anchor > cursor) {
271 const int length = MAX_MESSAGE_SIZE;
273 anchor -= cursor;
274 text = text.mid(cursor, anchor);
275 cursor = 0;
276 } else {
277 const int length = -MAX_MESSAGE_SIZE;
279 cursor -= anchor;
280 text = text.mid(anchor, cursor);
281 anchor = 0;
282 }
283 } else {
284 const int offset = (MAX_MESSAGE_SIZE - selectionLength) / 2;
285
287 int textEnd = QWaylandInputMethodEventBuilder::trimmedIndexFromWayland(text, MAX_MESSAGE_SIZE, textStart);
288
289 anchor -= textStart;
290 cursor -= textStart;
291 text = text.mid(textStart, textEnd - textStart);
292 }
293 }
294 qCDebug(qLcQpaWaylandTextInput) << "Modified surrounding_text: " << text << cursor << anchor;
295
297 const int anchorPos = QWaylandInputMethodEventBuilder::indexToWayland(text, anchor);
298
299 if (m_surroundingText != text || m_cursorPos != cursorPos || m_anchorPos != anchorPos) {
300 qCDebug(qLcQpaWaylandTextInput) << "Current surrounding_text: " << m_surroundingText << m_cursorPos << m_anchorPos;
301 qCDebug(qLcQpaWaylandTextInput) << "New surrounding_text: " << text << cursorPos << anchorPos;
302
303 set_surrounding_text(text, cursorPos, anchorPos);
304
305 // A workaround in the case of reselection
306 // It will work when re-clicking a preedit text
307 if (m_condReselection) {
308 qCDebug(qLcQpaWaylandTextInput) << "\"commit\" is disabled when Reselection by changing focus";
309 m_condReselection = false;
310 needsCommit = false;
311
312 }
313
314 m_surroundingText = text;
315 m_cursorPos = cursorPos;
316 m_anchorPos = anchorPos;
317 m_cursor = cursor;
318 }
319 }
320
321 if (queries & Qt::ImHints) {
322 QWaylandInputMethodContentType contentType = QWaylandInputMethodContentType::convertV4(static_cast<Qt::InputMethodHints>(event.value(Qt::ImHints).toInt()));
323 qCDebug(qLcQpaWaylandTextInput) << m_contentHint << contentType.hint;
324 qCDebug(qLcQpaWaylandTextInput) << m_contentPurpose << contentType.purpose;
325
326 if (m_contentHint != contentType.hint || m_contentPurpose != contentType.purpose) {
327 qCDebug(qLcQpaWaylandTextInput) << "set_content_type: " << contentType.hint << contentType.purpose;
328 set_content_type(contentType.hint, contentType.purpose);
329
330 m_contentHint = contentType.hint;
331 m_contentPurpose = contentType.purpose;
332 needsCommit = true;
333 }
334 }
335
336 if (needsCommit
338 commit();
339}
340
342{
344 qCWarning(qLcQpaWaylandTextInput) << "QWaylandTextInputV4: Input protocol \"text-input-unstable-v4-wip\" does not support setting cursor inside preedit. Use qt-text-input-method-unstable-v1 instead for full support of Qt input method events.";
345}
346
348{
349 qCWarning(qLcQpaWaylandTextInput) << "QWaylandTextInputV4: Input protocol \"text-input-unstable-v4-wip\" does not support querying input method visibility. Use qt-text-input-method-unstable-v1 instead for full support of Qt input method events.";
350 return false;
351}
352
354{
355 qCDebug(qLcQpaWaylandTextInput) << Q_FUNC_INFO;
356 return m_cursorRect;
357}
358
360{
361 qCWarning(qLcQpaWaylandTextInput) << "QWaylandTextInputV4: Input protocol \"text-input-unstable-v4-wip\" does not support querying input language. Use qt-text-input-method-unstable-v1 instead for full support of Qt input method events.";
362 return QLocale();
363}
364
366{
367 qCWarning(qLcQpaWaylandTextInput) << "QWaylandTextInputV4: Input protocol \"text-input-unstable-v4-wip\" does not support querying input direction. Use qt-text-input-method-unstable-v1 instead for full support of Qt input method events.";
368 return Qt::LeftToRight;
369}
370
371}
372
qsizetype size() const noexcept
Returns the number of bytes in this byte array.
Definition qbytearray.h:474
static bool sendEvent(QObject *receiver, QEvent *event)
Sends event event directly to receiver receiver, using the notify() function.
static QObject * focusObject()
Returns the QObject in currently active window that will be final receiver of events tied to focus,...
static QWindow * focusWindow()
Returns the QWindow that receives events tied to focus, such as key events.
static QInputMethod * inputMethod()
returns the input method.
The QInputMethodEvent class provides parameters for input method events.
Definition qevent.h:624
The QInputMethodQueryEvent class provides an event sent by the input context to input objects.
Definition qevent.h:678
QTransform inputItemTransform() const
Returns the transformation from input item coordinates to the window coordinates.
Definition qlist.h:74
void append(parameter_type t)
Definition qlist.h:441
\inmodule QtCore
Definition qmargins.h:23
constexpr int left() const noexcept
Returns the left margin.
Definition qmargins.h:110
constexpr int top() const noexcept
Returns the top margin.
Definition qmargins.h:113
\inmodule QtCore
Definition qobject.h:90
\inmodule QtCore\reentrant
Definition qrect.h:483
\inmodule QtCore\reentrant
Definition qrect.h:30
constexpr int height() const noexcept
Returns the height of the rectangle.
Definition qrect.h:238
constexpr int x() const noexcept
Returns the x-coordinate of the rectangle's left edge.
Definition qrect.h:184
constexpr int width() const noexcept
Returns the width of the rectangle.
Definition qrect.h:235
constexpr QRect translated(int dx, int dy) const noexcept
Returns a copy of the rectangle that is translated dx along the x axis and dy along the y axis,...
Definition qrect.h:260
constexpr int y() const noexcept
Returns the y-coordinate of the rectangle's top edge.
Definition qrect.h:187
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:127
void clear()
Clears the contents of the string and makes it null.
Definition qstring.h:1107
QString mid(qsizetype position, qsizetype n=-1) const
Returns a string that contains n characters of this string, starting at the specified position index.
Definition qstring.cpp:5204
QByteArray toUtf8() const &
Definition qstring.h:563
QRect mapRect(const QRect &) const
This is an overloaded member function, provided for convenience. It differs from the above function o...
static int indexFromWayland(const QString &text, int length, int base=0)
static int trimmedIndexFromWayland(const QString &text, int length, int base=0)
static int indexToWayland(const QString &text, int length, int base=0)
void zwp_text_input_v4_leave(struct ::wl_surface *surface) override
void zwp_text_input_v4_enter(struct ::wl_surface *surface) override
void disableSurface(::wl_surface *surface) override
Qt::LayoutDirection inputDirection() const override
void setCursorInsidePreedit(int cursor) override
void zwp_text_input_v4_done(uint32_t serial) override
void zwp_text_input_v4_preedit_string(const QString &text, int32_t cursor_begin, int32_t cursor_end) override
void zwp_text_input_v4_delete_surrounding_text(uint32_t before_length, uint32_t after_length) override
void updateState(Qt::InputMethodQueries queries, uint32_t flags) override
void enableSurface(::wl_surface *surface) override
void zwp_text_input_v4_commit_string(const QString &text) override
QString text
QCursor cursor
struct wl_display * display
Definition linuxdmabuf.h:41
Combined button and popup list for selecting options.
@ ImSurroundingText
@ ImCursorPosition
@ ImAnchorPosition
@ ImCursorRectangle
@ ImHints
@ ImEnabled
LayoutDirection
@ LeftToRight
#define Q_FUNC_INFO
#define Q_LOGGING_CATEGORY(name,...)
#define qCWarning(category,...)
#define qCDebug(category,...)
constexpr const T & qMin(const T &a, const T &b)
Definition qminmax.h:40
constexpr const T & qMax(const T &a, const T &b)
Definition qminmax.h:42
GLuint64 GLenum void * handle
GLenum GLuint GLenum GLsizei length
GLbitfield flags
GLboolean enable
GLenum GLuint GLintptr offset
GLint GLsizei GLsizei GLenum format
struct _cl_event * event
#define Q_UNUSED(x)
aWidget window() -> setWindowTitle("New Window Title")
[2]
static QWaylandInputMethodContentType convertV4(Qt::InputMethodHints hints)