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#include <qpa/qplatforminputcontext.h>
4
5#include "qwaylandtextinputv1_p.h"
6
7#include "qwaylandinputcontext_p.h"
8#include "qwaylandwindow_p.h"
9#include "qwaylandinputmethodeventbuilder_p.h"
10
11#include <QtCore/qloggingcategory.h>
12#include <QtGui/QGuiApplication>
13#include <QtGui/private/qguiapplication_p.h>
14#include <QtGui/private/qhighdpiscaling_p.h>
15#include <QtGui/qpa/qplatformintegration.h>
16#include <QtGui/qevent.h>
17#include <QtGui/qwindow.h>
18#include <QTextCharFormat>
19#include <QList>
20#include <QRectF>
21#include <QLocale>
22
23QT_BEGIN_NAMESPACE
24
25namespace QtWaylandClient {
26
27namespace {
28
29const Qt::InputMethodQueries supportedQueries1 = Qt::ImEnabled |
30 Qt::ImSurroundingText |
31 Qt::ImCursorPosition |
32 Qt::ImAnchorPosition |
33 Qt::ImHints |
34 Qt::ImCursorRectangle |
35 Qt::ImPreferredLanguage;
36}
37
38QWaylandTextInputv1::QWaylandTextInputv1(QWaylandDisplay *display, struct ::zwp_text_input_v1 *text_input)
39 : QtWayland::zwp_text_input_v1(text_input)
40{
41 Q_UNUSED(display);
42}
43
44QWaylandTextInputv1::~QWaylandTextInputv1()
45{
46 if (m_resetCallback)
47 wl_callback_destroy(m_resetCallback);
48 zwp_text_input_v1_destroy(object());
49}
50
51void QWaylandTextInputv1::reset()
52{
53 m_builder.reset();
54 m_preeditCommit = QString();
55 updateState(queries: Qt::ImQueryAll, flags: QWaylandTextInputInterface::update_state_reset);
56}
57
58void QWaylandTextInputv1::commit()
59{
60 if (QObject *o = QGuiApplication::focusObject()) {
61 QInputMethodEvent event;
62 event.setCommitString(commitString: m_preeditCommit);
63 QCoreApplication::sendEvent(receiver: o, event: &event);
64 }
65
66 reset();
67}
68
69const wl_callback_listener QWaylandTextInputv1::callbackListener = {
70 QWaylandTextInputv1::resetCallback
71};
72
73void QWaylandTextInputv1::resetCallback(void *data, wl_callback *, uint32_t)
74{
75 QWaylandTextInputv1 *self = static_cast<QWaylandTextInputv1*>(data);
76
77 if (self->m_resetCallback) {
78 wl_callback_destroy(self->m_resetCallback);
79 self->m_resetCallback = nullptr;
80 }
81}
82
83void QWaylandTextInputv1::updateState(Qt::InputMethodQueries queries, uint32_t flags)
84{
85 if (!QGuiApplication::focusObject())
86 return;
87
88 if (!QGuiApplication::focusWindow() || !QGuiApplication::focusWindow()->handle())
89 return;
90
91 auto *window = static_cast<QWaylandWindow *>(QGuiApplication::focusWindow()->handle());
92 auto *surface = window->wlSurface();
93 if (!surface || (surface != m_surface))
94 return;
95
96 queries &= supportedQueries1;
97
98 // Surrounding text, cursor and anchor positions are transferred together
99 if ((queries & Qt::ImSurroundingText) || (queries & Qt::ImCursorPosition) || (queries & Qt::ImAnchorPosition))
100 queries |= Qt::ImSurroundingText | Qt::ImCursorPosition | Qt::ImAnchorPosition;
101
102 QInputMethodQueryEvent event(queries);
103 QCoreApplication::sendEvent(receiver: QGuiApplication::focusObject(), event: &event);
104
105 if ((queries & Qt::ImSurroundingText) || (queries & Qt::ImCursorPosition) || (queries & Qt::ImAnchorPosition)) {
106 QString text = event.value(query: Qt::ImSurroundingText).toString();
107 int cursor = event.value(query: Qt::ImCursorPosition).toInt();
108 int anchor = event.value(query: Qt::ImAnchorPosition).toInt();
109
110 // Make sure text is not too big
111 if (text.toUtf8().size() > 2048) {
112 int c = qAbs(t: cursor - anchor) <= 512 ? qMin(a: cursor, b: anchor) + qAbs(t: cursor - anchor) / 2: cursor;
113
114 const int offset = c - qBound(min: 0, val: c, max: 512 - qMin(a: text.size() - c, b: 256));
115 text = text.mid(position: offset + c - 256, n: 512);
116 cursor -= offset;
117 anchor -= offset;
118 }
119
120 set_surrounding_text(text, QWaylandInputMethodEventBuilder::indexToWayland(text, length: cursor), QWaylandInputMethodEventBuilder::indexToWayland(text, length: anchor));
121 }
122
123 if (queries & Qt::ImHints) {
124 QWaylandInputMethodContentType contentType = QWaylandInputMethodContentType::convert(hints: static_cast<Qt::InputMethodHints>(event.value(query: Qt::ImHints).toInt()));
125 set_content_type(contentType.hint, contentType.purpose);
126 }
127
128 if (queries & Qt::ImCursorRectangle) {
129 const QRect &cRect = event.value(query: Qt::ImCursorRectangle).toRect();
130 const QRect &windowRect = QGuiApplication::inputMethod()->inputItemTransform().mapRect(cRect);
131 const QRect &nativeRect = QHighDpi::toNativePixels(value: windowRect, context: QGuiApplication::focusWindow());
132 const QMargins margins = window->clientSideMargins();
133 const QRect &surfaceRect = nativeRect.translated(dx: margins.left(), dy: margins.top());
134 set_cursor_rectangle(surfaceRect.x(), surfaceRect.y(), surfaceRect.width(), surfaceRect.height());
135 }
136
137 if (queries & Qt::ImPreferredLanguage) {
138 const QString &language = event.value(query: Qt::ImPreferredLanguage).toString();
139 set_preferred_language(language);
140 }
141
142 if (flags == QWaylandTextInputInterface::update_state_reset)
143 QtWayland::zwp_text_input_v1::reset();
144 else
145 commit_state(m_serial);
146}
147
148void QWaylandTextInputv1::setCursorInsidePreedit(int)
149{
150 // Not supported yet
151}
152
153bool QWaylandTextInputv1::isInputPanelVisible() const
154{
155 return m_inputPanelVisible;
156}
157
158QRectF QWaylandTextInputv1::keyboardRect() const
159{
160 return m_keyboardRectangle;
161}
162
163QLocale QWaylandTextInputv1::locale() const
164{
165 return m_locale;
166}
167
168Qt::LayoutDirection QWaylandTextInputv1::inputDirection() const
169{
170 return m_inputDirection;
171}
172
173void QWaylandTextInputv1::zwp_text_input_v1_enter(::wl_surface *surface)
174{
175 m_surface = surface;
176
177 updateState(queries: Qt::ImQueryAll, flags: QWaylandTextInputInterface::update_state_reset);
178}
179
180void QWaylandTextInputv1::zwp_text_input_v1_leave()
181{
182 m_surface = nullptr;
183}
184
185void QWaylandTextInputv1::zwp_text_input_v1_modifiers_map(wl_array *map)
186{
187 const QList<QByteArray> modifiersMap = QByteArray::fromRawData(data: static_cast<const char*>(map->data), size: map->size).split('\0');
188
189 m_modifiersMap.clear();
190
191 for (const QByteArray &modifier : modifiersMap) {
192 if (modifier == "Shift")
193 m_modifiersMap.append(Qt::ShiftModifier);
194 else if (modifier == "Control")
195 m_modifiersMap.append(Qt::ControlModifier);
196 else if (modifier == "Alt")
197 m_modifiersMap.append(Qt::AltModifier);
198 else if (modifier == "Mod1")
199 m_modifiersMap.append(Qt::AltModifier);
200 else if (modifier == "Mod4")
201 m_modifiersMap.append(Qt::MetaModifier);
202 else
203 m_modifiersMap.append(Qt::NoModifier);
204 }
205}
206
207void QWaylandTextInputv1::zwp_text_input_v1_input_panel_state(uint32_t visible)
208{
209 const bool inputPanelVisible = (visible == 1);
210 if (m_inputPanelVisible != inputPanelVisible) {
211 m_inputPanelVisible = inputPanelVisible;
212 QGuiApplicationPrivate::platformIntegration()->inputContext()->emitInputPanelVisibleChanged();
213 }
214}
215
216void QWaylandTextInputv1::zwp_text_input_v1_preedit_string(uint32_t serial, const QString &text, const QString &commit)
217{
218 m_serial = serial;
219
220 if (m_resetCallback) {
221 qCDebug(qLcQpaInputMethods()) << "discard preedit_string: reset not confirmed";
222 m_builder.reset();
223 return;
224 }
225
226 if (!QGuiApplication::focusObject())
227 return;
228
229 QInputMethodEvent *event = m_builder.buildPreedit(text);
230
231 m_builder.reset();
232 m_preeditCommit = commit;
233
234 QCoreApplication::sendEvent(receiver: QGuiApplication::focusObject(), event);
235 delete event;
236}
237
238void QWaylandTextInputv1::zwp_text_input_v1_preedit_styling(uint32_t index, uint32_t length, uint32_t style)
239{
240 m_builder.addPreeditStyling(index, length, style);
241}
242
243void QWaylandTextInputv1::zwp_text_input_v1_preedit_cursor(int32_t index)
244{
245 m_builder.setPreeditCursor(index);
246}
247
248void QWaylandTextInputv1::zwp_text_input_v1_commit_string(uint32_t serial, const QString &text)
249{
250 m_serial = serial;
251
252 if (m_resetCallback) {
253 qCDebug(qLcQpaInputMethods()) << "discard commit_string: reset not confirmed";
254 m_builder.reset();
255 return;
256 }
257
258 if (!QGuiApplication::focusObject())
259 return;
260
261 // When committing the text, the preeditString needs to be reset, to prevent it to be
262 // send again in the commit() function
263 m_preeditCommit.clear();
264
265 QInputMethodEvent *event = m_builder.buildCommit(text);
266
267 m_builder.reset();
268
269 QCoreApplication::sendEvent(receiver: QGuiApplication::focusObject(), event);
270 delete event;
271}
272
273void QWaylandTextInputv1::zwp_text_input_v1_cursor_position(int32_t index, int32_t anchor)
274{
275 m_builder.setCursorPosition(index, anchor);
276}
277
278void QWaylandTextInputv1::zwp_text_input_v1_delete_surrounding_text(int32_t before_length, uint32_t after_length)
279{
280 //before_length is negative, but the builder expects it to be positive
281 m_builder.setDeleteSurroundingText(beforeLength: -before_length, afterLength: after_length);
282}
283
284void QWaylandTextInputv1::zwp_text_input_v1_keysym(uint32_t serial, uint32_t time, uint32_t sym, uint32_t state, uint32_t modifiers)
285{
286 m_serial = serial;
287
288#if QT_CONFIG(xkbcommon)
289 if (m_resetCallback) {
290 qCDebug(qLcQpaInputMethods()) << "discard keysym: reset not confirmed";
291 return;
292 }
293
294 if (!QGuiApplication::focusWindow())
295 return;
296
297 Qt::KeyboardModifiers qtModifiers = modifiersToQtModifiers(modifiers);
298
299 QEvent::Type type = state == WL_KEYBOARD_KEY_STATE_PRESSED ? QEvent::KeyPress : QEvent::KeyRelease;
300 QString text = QXkbCommon::lookupStringNoKeysymTransformations(keysym: sym);
301 int qtkey = QXkbCommon::keysymToQtKey(keysym: sym, modifiers: qtModifiers);
302
303 QWindowSystemInterface::handleKeyEvent(window: QGuiApplication::focusWindow(),
304 timestamp: time, t: type, k: qtkey, mods: qtModifiers, text);
305#else
306 Q_UNUSED(time);
307 Q_UNUSED(sym);
308 Q_UNUSED(state);
309 Q_UNUSED(modifiers);
310#endif
311}
312
313void QWaylandTextInputv1::zwp_text_input_v1_language(uint32_t serial, const QString &language)
314{
315 m_serial = serial;
316
317 if (m_resetCallback) {
318 qCDebug(qLcQpaInputMethods()) << "discard language: reset not confirmed";
319 return;
320 }
321
322 const QLocale locale(language);
323 if (m_locale != locale) {
324 m_locale = locale;
325 QGuiApplicationPrivate::platformIntegration()->inputContext()->emitLocaleChanged();
326 }
327}
328
329void QWaylandTextInputv1::zwp_text_input_v1_text_direction(uint32_t serial, uint32_t direction)
330{
331 m_serial = serial;
332
333 if (m_resetCallback) {
334 qCDebug(qLcQpaInputMethods()) << "discard text_direction: reset not confirmed";
335 return;
336 }
337
338 const Qt::LayoutDirection inputDirection = (direction == text_direction_auto) ? Qt::LayoutDirectionAuto :
339 (direction == text_direction_ltr) ? Qt::LeftToRight :
340 (direction == text_direction_rtl) ? Qt::RightToLeft : Qt::LayoutDirectionAuto;
341 if (m_inputDirection != inputDirection) {
342 m_inputDirection = inputDirection;
343 QGuiApplicationPrivate::platformIntegration()->inputContext()->emitInputDirectionChanged(newDirection: m_inputDirection);
344 }
345}
346
347Qt::KeyboardModifiers QWaylandTextInputv1::modifiersToQtModifiers(uint32_t modifiers)
348{
349 Qt::KeyboardModifiers ret = Qt::NoModifier;
350 for (int i = 0; i < m_modifiersMap.size(); ++i) {
351 if (modifiers & (1 << i)) {
352 ret |= m_modifiersMap[i];
353 }
354 }
355 return ret;
356}
357
358}
359
360QT_END_NAMESPACE
361
362

Provided by KDAB

Privacy Policy
Learn to use CMake with our Intro Training
Find out more

source code of qtwayland/src/client/qwaylandtextinputv1.cpp