144 qCDebug(qLcQpaWaylandTextInput) << Q_FUNC_INFO <<
"with serial" << serial << m_currentSerial;
151 if (m_cursorPos != m_anchorPos && (m_pendingDeleteBeforeText != 0 || m_pendingDeleteAfterText != 0)) {
152 qCDebug(qLcQpaWaylandTextInput) << Q_FUNC_INFO <<
"Ignore done";
153 m_pendingDeleteBeforeText = 0;
154 m_pendingDeleteAfterText = 0;
155 m_pendingPreeditString.clear();
156 m_pendingCommitString.clear();
160 QObject *focusObject = QGuiApplication::focusObject();
165 qCWarning(qLcQpaWaylandTextInput) << Q_FUNC_INFO << serial <<
"Surface is not enabled yet";
169 if ((m_pendingPreeditString == m_currentPreeditString)
170 && (m_pendingCommitString.isEmpty() && m_pendingDeleteBeforeText == 0
171 && m_pendingDeleteAfterText == 0)) {
173 m_pendingPreeditString.clear();
177 const int newCursorIndex = QWaylandInputMethodEventBuilder::indexFromWayland(m_pendingPreeditString.text, m_pendingPreeditString.cursorBegin);
178 qCDebug(qLcQpaWaylandTextInput) << Q_FUNC_INFO <<
"PREEDIT" << m_pendingPreeditString.text << newCursorIndex;
180 QList<QInputMethodEvent::Attribute> attributes;
182 if (m_pendingPreeditString.cursorBegin == -1 &&
183 m_pendingPreeditString.cursorEnd == -1) {
184 QInputMethodEvent::Attribute attribute(QInputMethodEvent::Cursor,
187 attributes.append(attribute);
188 }
else if (m_pendingPreeditString.cursorBegin != 0 ||
189 m_pendingPreeditString.cursorEnd != 0) {
192 QInputMethodEvent::Attribute attribute1(QInputMethodEvent::Cursor,
195 attributes.append(attribute1);
198 if (version() == 1) {
200 QTextCharFormat format;
201 format.setFontUnderline(
true);
202 format.setUnderlineStyle(QTextCharFormat::SingleUnderline);
203 QInputMethodEvent::Attribute attribute2(QInputMethodEvent::TextFormat,
205 m_pendingPreeditString.text.length(), format);
206 attributes.append(attribute2);
208 for (
const StyleHint &prededitStyle : std::as_const(m_pendingPreeditString.styleHints)) {
209 int begin = QWaylandInputMethodEventBuilder::indexFromWayland(m_pendingPreeditString.text, prededitStyle.begin);
210 int end = QWaylandInputMethodEventBuilder::indexFromWayland(m_pendingPreeditString.text, prededitStyle.end);
211 QTextCharFormat format;
214 switch (prededitStyle.hint) {
215 case preedit_hint_whole:
216 format.setUnderlineStyle(QTextCharFormat::SingleUnderline);
218 case preedit_hint_selection:
219 format.setForeground(QPalette().highlightedText());
220 format.setBackground(QPalette().highlight());
222 case preedit_hint_prediction:
224 format.setBackground(QPalette().placeholderText());
226 case preedit_hint_prefix:
227 format.setForeground(QColor(
"#F90F0F"));
229 case preedit_hint_suffix:
230 format.setForeground(QColor(
"#1EDC1A"));
232 case preedit_hint_spelling_error:
233 format.setUnderlineStyle(QTextCharFormat::WaveUnderline);
234 format.setUnderlineColor(QColor(
"#A40000"));
236 case preedit_hint_compose_error:
237 format.setUnderlineStyle(QTextCharFormat::WaveUnderline);
238 format.setUnderlineColor(QColor(
"#FF00FF"));
242 QInputMethodEvent::Attribute attribute2(QInputMethodEvent::TextFormat,
246 attributes.append(attribute2);
250 QInputMethodEvent event(m_pendingPreeditString.text, attributes);
252 qCDebug(qLcQpaWaylandTextInput) << Q_FUNC_INFO <<
"DELETE" << m_pendingDeleteBeforeText << m_pendingDeleteAfterText;
253 qCDebug(qLcQpaWaylandTextInput) << Q_FUNC_INFO <<
"COMMIT" << m_pendingCommitString;
256 int replaceLength = 0;
257 if (m_pendingDeleteBeforeText != 0 || m_pendingDeleteAfterText != 0) {
260 m_condReselection =
true;
261 const QByteArray &utf8 = QStringView{m_surroundingText}.toUtf8();
262 if (m_cursorPos <
int(m_pendingDeleteBeforeText)) {
263 replaceFrom = -QString::fromUtf8(QByteArrayView{utf8}.first(m_pendingDeleteBeforeText)).size();
264 replaceLength = QString::fromUtf8(QByteArrayView{utf8}.first(m_pendingDeleteBeforeText + m_pendingDeleteAfterText)).size();
266 replaceFrom = -QString::fromUtf8(QByteArrayView{utf8}.sliced(m_cursorPos - m_pendingDeleteBeforeText, m_pendingDeleteBeforeText)).size();
267 replaceLength = QString::fromUtf8(QByteArrayView{utf8}.sliced(m_cursorPos - m_pendingDeleteBeforeText, m_pendingDeleteBeforeText + m_pendingDeleteAfterText)).size();
271 qCDebug(qLcQpaWaylandTextInput) << Q_FUNC_INFO <<
"DELETE from " << replaceFrom <<
" length " << replaceLength;
272 event.setCommitString(m_pendingCommitString,
275 m_currentPreeditString = m_pendingPreeditString;
276 m_pendingPreeditString.clear();
277 m_pendingCommitString.clear();
278 m_pendingDeleteBeforeText = 0;
279 m_pendingDeleteAfterText = 0;
280 QCoreApplication::sendEvent(focusObject, &event);
282 if (serial == m_currentSerial)
283 updateState(supportedQueries3, update_state_full);
342 qCDebug(qLcQpaWaylandTextInput) << Q_FUNC_INFO << queries << flags;
344 if (!QGuiApplication::focusObject())
347 if (!QGuiApplication::focusWindow() || !QGuiApplication::focusWindow()->handle())
350 auto *window =
static_cast<QWaylandWindow *>(QGuiApplication::focusWindow()->handle());
351 auto *surface = window->wlSurface();
352 if (!surface || (surface != m_surface))
355 queries &= supportedQueries3;
356 bool needsCommit =
false;
358 QInputMethodQueryEvent event(queries);
359 QCoreApplication::sendEvent(QGuiApplication::focusObject(), &event);
362 if (!(queries & Qt::ImSurroundingText) && event.value(Qt::ImSurroundingText).toString().isEmpty()) {
366 if (queries & Qt::ImCursorRectangle) {
367 const QRect &cRect = event.value(Qt::ImCursorRectangle).toRect();
368 const QRect &windowRect = QGuiApplication::inputMethod()->inputItemTransform().mapRect(cRect);
369 const QRect &nativeRect = QHighDpi::toNativePixels(windowRect, QGuiApplication::focusWindow());
370 const QMargins margins = window->clientSideMargins();
371 const QRect &surfaceRect = nativeRect.translated(margins.left(), margins.top());
372 if (surfaceRect != m_cursorRect) {
373 set_cursor_rectangle(surfaceRect.x(), surfaceRect.y(), surfaceRect.width(), surfaceRect.height());
374 m_cursorRect = surfaceRect;
379 if ((queries & Qt::ImSurroundingText) || (queries & Qt::ImCursorPosition) || (queries & Qt::ImAnchorPosition)) {
380 QString text = event.value(Qt::ImSurroundingText).toString();
381 int cursor = event.value(Qt::ImCursorPosition).toInt();
382 int anchor = event.value(Qt::ImAnchorPosition).toInt();
384 qCDebug(qLcQpaWaylandTextInput) <<
"Original surrounding_text from InputMethodQuery: " << text << cursor << anchor;
389 const int MAX_MESSAGE_SIZE = 4000;
391 const QByteArray utf8 = text.toUtf8();
392 const int textSize = utf8.size();
393 if (textSize > MAX_MESSAGE_SIZE) {
394 qCDebug(qLcQpaWaylandTextInput) <<
"SurroundText size is over "
396 <<
" byte, some text will be clipped.";
397 const int selectionStart = qMin(cursor, anchor);
398 const int selectionEnd = qMax(cursor, anchor);
399 const int selectionLength = selectionEnd - selectionStart;
400 QByteArray selection = QStringView{text}.sliced(selectionStart, selectionLength).toUtf8();
401 const int selectionSize = selection.size();
404 if (selectionSize > MAX_MESSAGE_SIZE) {
405 if (anchor > cursor) {
407 anchor = MAX_MESSAGE_SIZE;
408 text = QString::fromUtf8(QByteArrayView{selection}.sliced(0, MAX_MESSAGE_SIZE));
411 cursor = MAX_MESSAGE_SIZE;
412 text = QString::fromUtf8(QByteArrayView{selection}.sliced(selectionSize - MAX_MESSAGE_SIZE, MAX_MESSAGE_SIZE));
420 int selEndSize = QStringView{text}.first(selectionEnd).toUtf8().size();
421 cursor = QWaylandInputMethodEventBuilder::indexToWayland(text, cursor);
422 anchor = QWaylandInputMethodEventBuilder::indexToWayland(text, anchor);
423 if (selEndSize < MAX_MESSAGE_SIZE) {
424 text = QString::fromUtf8(QByteArrayView{utf8}.first(MAX_MESSAGE_SIZE));
426 const int startOffset = selEndSize - MAX_MESSAGE_SIZE;
427 text = QString::fromUtf8(QByteArrayView{utf8}.sliced(startOffset, MAX_MESSAGE_SIZE));
428 cursor -= startOffset;
429 anchor -= startOffset;
433 cursor = QWaylandInputMethodEventBuilder::indexToWayland(text, cursor);
434 anchor = QWaylandInputMethodEventBuilder::indexToWayland(text, anchor);
436 qCDebug(qLcQpaWaylandTextInput) <<
"Modified surrounding_text: " << text << cursor << anchor;
438 if (m_surroundingText != text || m_cursorPos != cursor || m_anchorPos != anchor) {
439 qCDebug(qLcQpaWaylandTextInput) <<
"Current surrounding_text: " << m_surroundingText << m_cursorPos << m_anchorPos;
440 qCDebug(qLcQpaWaylandTextInput) <<
"New surrounding_text: " << text << cursor << anchor;
442 set_surrounding_text(text, cursor, anchor);
446 if (m_condReselection) {
447 qCDebug(qLcQpaWaylandTextInput) <<
"\"commit\" is disabled when Reselection by changing focus";
448 m_condReselection =
false;
453 m_surroundingText = text;
454 m_cursorPos = cursor;
455 m_anchorPos = anchor;
460 if (queries & Qt::ImHints) {
461 QWaylandInputMethodContentType contentType = QWaylandInputMethodContentType::convertV3(
static_cast<Qt::InputMethodHints>(event.value(Qt::ImHints).toInt()));
463 if (version() >= ZWP_TEXT_INPUT_V3_CONTENT_HINT_PREEDIT_SHOWN_SINCE_VERSION)
464 contentType.hint |= ZWP_TEXT_INPUT_V3_CONTENT_HINT_PREEDIT_SHOWN;
466 qCDebug(qLcQpaWaylandTextInput) << m_contentHint << contentType.hint;
467 qCDebug(qLcQpaWaylandTextInput) << m_contentPurpose << contentType.purpose;
469 if (m_contentHint != contentType.hint || m_contentPurpose != contentType.purpose) {
470 qCDebug(qLcQpaWaylandTextInput) <<
"set_content_type: " << contentType.hint << contentType.purpose;
471 set_content_type(contentType.hint, contentType.purpose);
473 m_contentHint = contentType.hint;
474 m_contentPurpose = contentType.purpose;