1// Copyright (C) 2020 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
4#include "qwaylandinputmethodcontext_p.h"
5#include "qwaylanddisplay_p.h"
6#include "qwaylandinputdevice_p.h"
7
8#include <QtGui/qguiapplication.h>
9#include <QtGui/qtextformat.h>
10#include <QtGui/private/qguiapplication_p.h>
11
12QT_BEGIN_NAMESPACE
13
14Q_DECLARE_LOGGING_CATEGORY(qLcQpaInputMethods)
15
16namespace QtWaylandClient {
17
18static constexpr int maxStringSize = 1000; // actual max is 4096/3
19
20QWaylandTextInputMethod::QWaylandTextInputMethod(QWaylandDisplay *display, struct ::qt_text_input_method_v1 *textInputMethod)
21 : QtWayland::qt_text_input_method_v1(textInputMethod)
22{
23 Q_UNUSED(display);
24}
25
26QWaylandTextInputMethod::~QWaylandTextInputMethod()
27{
28}
29
30void QWaylandTextInputMethod::text_input_method_v1_visible_changed(int32_t visible)
31{
32 if (m_isVisible != visible) {
33 m_isVisible = visible;
34 QGuiApplicationPrivate::platformIntegration()->inputContext()->emitInputPanelVisibleChanged();
35 }
36}
37
38void QWaylandTextInputMethod::text_input_method_v1_locale_changed(const QString &localeName)
39{
40 m_locale = QLocale(localeName);
41}
42
43void QWaylandTextInputMethod::text_input_method_v1_input_direction_changed(int32_t inputDirection)
44{
45 m_layoutDirection = Qt::LayoutDirection(inputDirection);
46}
47
48void QWaylandTextInputMethod::text_input_method_v1_keyboard_rectangle_changed(wl_fixed_t x, wl_fixed_t y, wl_fixed_t width, wl_fixed_t height)
49{
50 const QRectF keyboardRectangle(wl_fixed_to_double(x),
51 wl_fixed_to_double(y),
52 wl_fixed_to_double(width),
53 wl_fixed_to_double(height));
54 if (m_keyboardRect != keyboardRectangle) {
55 m_keyboardRect = keyboardRectangle;
56 QGuiApplicationPrivate::platformIntegration()->inputContext()->emitKeyboardRectChanged();
57 }
58}
59
60void QWaylandTextInputMethod::text_input_method_v1_start_input_method_event(uint32_t serial, int32_t surrounding_text_offset)
61{
62 if (m_pendingInputMethodEvents.contains(key: serial)) {
63 qCWarning(qLcQpaInputMethods) << "Input method event with serial" << serial << "already started";
64 return;
65 }
66
67 m_pendingInputMethodEvents[serial] = QList<QInputMethodEvent::Attribute>{};
68 m_offsetFromCompositor[serial] = surrounding_text_offset;
69}
70
71// We need to keep surrounding text below maxStringSize characters, with cursorPos centered in that substring
72
73static int calculateOffset(const QString &text, int cursorPos)
74{
75 int size = text.size();
76 int halfSize = maxStringSize/2;
77 if (size <= maxStringSize || cursorPos < halfSize)
78 return 0;
79 if (cursorPos > size - halfSize)
80 return size - maxStringSize;
81 return cursorPos - halfSize;
82}
83
84static QString mapSurroundingTextToCompositor(const QString &s, int offset)
85{
86 return s.mid(position: offset, n: maxStringSize);
87}
88
89static int mapPositionToCompositor(int pos, int offset)
90{
91 return pos - offset;
92}
93
94static int mapPositionFromCompositor(int pos, int offset)
95{
96 return pos + offset;
97}
98
99void QWaylandTextInputMethod::text_input_method_v1_input_method_event_attribute(uint32_t serial, int32_t type, int32_t start, int32_t length, const QString &value)
100{
101 if (!m_pendingInputMethodEvents.contains(key: serial)) {
102 qCWarning(qLcQpaInputMethods) << "Input method event with serial" << serial << "does not exist";
103 return;
104 }
105
106 int startMapped = mapPositionFromCompositor(pos: start, offset: m_offsetFromCompositor[serial]);
107 QList<QInputMethodEvent::Attribute> &attributes = m_pendingInputMethodEvents[serial];
108 switch (type) {
109 case QInputMethodEvent::Selection:
110 attributes.append(t: QInputMethodEvent::Attribute(QInputMethodEvent::AttributeType(type), startMapped, length));
111 break;
112 case QInputMethodEvent::Cursor:
113 attributes.append(t: QInputMethodEvent::Attribute(QInputMethodEvent::AttributeType(type), start, length, QColor::fromString(name: value)));
114 break;
115 case QInputMethodEvent::TextFormat:
116 {
117 QTextCharFormat textFormat;
118 textFormat.setProperty(propertyId: QTextFormat::FontUnderline, value: true);
119 textFormat.setProperty(propertyId: QTextFormat::TextUnderlineStyle, value: QTextCharFormat::SingleUnderline);
120 attributes.append(t: QInputMethodEvent::Attribute(QInputMethodEvent::AttributeType(type), start, length, textFormat));
121 break;
122 }
123 case QInputMethodEvent::Language:
124 case QInputMethodEvent::Ruby:
125 attributes.append(t: QInputMethodEvent::Attribute(QInputMethodEvent::AttributeType(type), start, length, value));
126 break;
127 };
128}
129
130void QWaylandTextInputMethod::sendInputState(QInputMethodQueryEvent *event, Qt::InputMethodQueries queries)
131{
132 int cursorPosition = event->value(query: Qt::ImCursorPosition).toInt();
133 int anchorPosition = event->value(query: Qt::ImAnchorPosition).toInt();
134 QString surroundingText = event->value(query: Qt::ImSurroundingText).toString();
135 int offset = calculateOffset(text: surroundingText, cursorPos: cursorPosition);
136
137 if (queries & Qt::ImCursorPosition)
138 update_cursor_position(mapPositionToCompositor(pos: cursorPosition, offset));
139 if (queries & Qt::ImSurroundingText)
140 update_surrounding_text(mapSurroundingTextToCompositor(s: surroundingText, offset), offset);
141 if (queries & Qt::ImAnchorPosition)
142 update_anchor_position(mapPositionToCompositor(pos: anchorPosition, offset));
143 if (queries & Qt::ImAbsolutePosition)
144 update_absolute_position(event->value(query: Qt::ImAbsolutePosition).toInt()); // do not map: this is the position in the whole document
145}
146
147
148void QWaylandTextInputMethod::text_input_method_v1_end_input_method_event(uint32_t serial, const QString &commitString, const QString &preeditString, int32_t replacementStart, int32_t replacementLength)
149{
150 if (!m_pendingInputMethodEvents.contains(key: serial)) {
151 qCWarning(qLcQpaInputMethods) << "Input method event with serial" << serial << "does not exist";
152 return;
153 }
154
155 QList<QInputMethodEvent::Attribute> attributes = m_pendingInputMethodEvents.take(key: serial);
156 m_offsetFromCompositor.remove(key: serial);
157 if (QGuiApplication::focusObject() != nullptr) {
158 QInputMethodEvent event(preeditString, attributes);
159 event.setCommitString(commitString, replaceFrom: replacementStart, replaceLength: replacementLength);
160 QCoreApplication::sendEvent(receiver: QGuiApplication::focusObject(), event: &event);
161 }
162
163 // Send current state to make sure it matches
164 if (QGuiApplication::focusObject() != nullptr) {
165 QInputMethodQueryEvent event(Qt::ImCursorPosition | Qt::ImSurroundingText | Qt::ImAnchorPosition | Qt::ImAbsolutePosition);
166 QCoreApplication::sendEvent(receiver: QGuiApplication::focusObject(), event: &event);
167 sendInputState(event: &event);
168 }
169
170 acknowledge_input_method();
171}
172
173void QWaylandTextInputMethod::text_input_method_v1_key(int32_t type,
174 int32_t key,
175 int32_t modifiers,
176 int32_t autoRepeat,
177 int32_t count,
178 int32_t nativeScanCode,
179 int32_t nativeVirtualKey,
180 int32_t nativeModifiers,
181 const QString &text)
182{
183 if (QGuiApplication::focusObject() != nullptr) {
184 QKeyEvent event(QKeyEvent::Type(type),
185 key,
186 Qt::KeyboardModifiers(modifiers),
187 nativeScanCode,
188 nativeVirtualKey,
189 nativeModifiers,
190 text,
191 autoRepeat,
192 count);
193 QCoreApplication::sendEvent(receiver: QGuiApplication::focusObject(), event: &event);
194 }
195}
196
197void QWaylandTextInputMethod::text_input_method_v1_enter(struct ::wl_surface *surface)
198{
199 m_surface = surface;
200}
201
202void QWaylandTextInputMethod::text_input_method_v1_leave(struct ::wl_surface *surface)
203{
204 if (surface != m_surface) {
205 qCWarning(qLcQpaInputMethods) << "Got leave event for surface without corresponding enter";
206 } else {
207 m_surface = nullptr;
208 }
209}
210
211QWaylandInputMethodContext::QWaylandInputMethodContext(QWaylandDisplay *display)
212 : m_display(display)
213{
214}
215
216QWaylandInputMethodContext::~QWaylandInputMethodContext()
217{
218}
219
220bool QWaylandInputMethodContext::isValid() const
221{
222 return m_display->textInputMethodManager() != nullptr;
223}
224
225void QWaylandInputMethodContext::reset()
226{
227 QWaylandTextInputMethod *inputMethod = textInputMethod();
228 if (inputMethod != nullptr)
229 inputMethod->reset();
230}
231
232void QWaylandInputMethodContext::commit()
233{
234 QWaylandTextInputMethod *inputMethod = textInputMethod();
235 if (inputMethod != nullptr)
236 inputMethod->commit();
237
238 m_display->forceRoundTrip();
239}
240
241void QWaylandInputMethodContext::update(Qt::InputMethodQueries queries)
242{
243 wl_surface *currentSurface = m_currentWindow != nullptr && m_currentWindow->handle() != nullptr
244 ? static_cast<QWaylandWindow *>(m_currentWindow->handle())->wlSurface()
245 : nullptr;
246 if (currentSurface != nullptr && !inputMethodAccepted()) {
247 textInputMethod()->disable(currentSurface);
248 m_currentWindow.clear();
249 } else if (currentSurface == nullptr && inputMethodAccepted()) {
250 QWindow *window = QGuiApplication::focusWindow();
251 currentSurface = window != nullptr && window->handle() != nullptr
252 ? static_cast<QWaylandWindow *>(window->handle())->wlSurface()
253 : nullptr;
254 if (currentSurface != nullptr) {
255 textInputMethod()->disable(currentSurface);
256 m_currentWindow = window;
257 }
258 }
259
260 queries &= (Qt::ImEnabled
261 | Qt::ImHints
262 | Qt::ImCursorRectangle
263 | Qt::ImCursorPosition
264 | Qt::ImSurroundingText
265 | Qt::ImCurrentSelection
266 | Qt::ImAnchorPosition
267 | Qt::ImTextAfterCursor
268 | Qt::ImTextBeforeCursor
269 | Qt::ImPreferredLanguage);
270
271 const Qt::InputMethodQueries queriesNeedingOffset = Qt::ImCursorPosition | Qt::ImSurroundingText | Qt::ImAnchorPosition;
272 if (queries & queriesNeedingOffset)
273 queries |= queriesNeedingOffset;
274
275 QWaylandTextInputMethod *inputMethod = textInputMethod();
276 if (inputMethod != nullptr && QGuiApplication::focusObject() != nullptr) {
277 QInputMethodQueryEvent event(queries);
278 QCoreApplication::sendEvent(receiver: QGuiApplication::focusObject(), event: &event);
279
280 inputMethod->start_update(int(queries));
281
282 if (queries & Qt::ImHints)
283 inputMethod->update_hints(event.value(query: Qt::ImHints).toInt());
284
285 if (queries & Qt::ImCursorRectangle) {
286 QRect rect = event.value(query: Qt::ImCursorRectangle).toRect();
287 inputMethod->update_cursor_rectangle(rect.x(), rect.y(), rect.width(), rect.height());
288 }
289
290 inputMethod->sendInputState(event: &event, queries);
291
292 if (queries & Qt::ImPreferredLanguage)
293 inputMethod->update_preferred_language(event.value(query: Qt::ImPreferredLanguage).toString());
294
295 inputMethod->end_update();
296
297 // ### Should we do a display sync here and ignore all events until it is received?
298 }
299}
300
301void QWaylandInputMethodContext::invokeAction(QInputMethod::Action action, int cursorPosition)
302{
303 QWaylandTextInputMethod *inputMethod = textInputMethod();
304 if (inputMethod != nullptr)
305 inputMethod->invoke_action(int(action), cursorPosition);
306}
307
308void QWaylandInputMethodContext::showInputPanel()
309{
310 QWaylandTextInputMethod *inputMethod = textInputMethod();
311 if (inputMethod != nullptr)
312 inputMethod->show_input_panel();
313}
314
315void QWaylandInputMethodContext::hideInputPanel()
316{
317 QWaylandTextInputMethod *inputMethod = textInputMethod();
318 if (inputMethod != nullptr)
319 inputMethod->hide_input_panel();
320}
321
322bool QWaylandInputMethodContext::isInputPanelVisible() const
323{
324 QWaylandTextInputMethod *inputMethod = textInputMethod();
325 if (inputMethod != nullptr)
326 return inputMethod->isVisible();
327 else
328 return false;
329}
330
331QRectF QWaylandInputMethodContext::keyboardRect() const
332{
333 QWaylandTextInputMethod *inputMethod = textInputMethod();
334 if (inputMethod != nullptr)
335 return inputMethod->keyboardRect();
336 else
337 return QRectF();
338}
339
340QLocale QWaylandInputMethodContext::locale() const
341{
342 QWaylandTextInputMethod *inputMethod = textInputMethod();
343 if (inputMethod != nullptr)
344 return inputMethod->locale();
345 else
346 return QLocale();
347}
348
349Qt::LayoutDirection QWaylandInputMethodContext::inputDirection() const
350{
351 QWaylandTextInputMethod *inputMethod = textInputMethod();
352 if (inputMethod != nullptr)
353 return inputMethod->inputDirection();
354 else
355 return Qt::LeftToRight;
356}
357
358void QWaylandInputMethodContext::setFocusObject(QObject *)
359{
360 QWaylandTextInputMethod *inputMethod = textInputMethod();
361 if (inputMethod == nullptr)
362 return;
363
364 QWindow *window = QGuiApplication::focusWindow();
365
366 if (m_currentWindow != nullptr && m_currentWindow->handle() != nullptr) {
367 if (m_currentWindow.data() != window || !inputMethodAccepted()) {
368 auto *surface = static_cast<QWaylandWindow *>(m_currentWindow->handle())->wlSurface();
369 if (surface)
370 inputMethod->disable(surface);
371 m_currentWindow.clear();
372 }
373 }
374
375 if (window != nullptr && window->handle() != nullptr && inputMethodAccepted()) {
376 if (m_currentWindow.data() != window) {
377 auto *surface = static_cast<QWaylandWindow *>(window->handle())->wlSurface();
378 if (surface != nullptr) {
379 inputMethod->enable(surface);
380 m_currentWindow = window;
381 }
382 }
383
384 update(queries: Qt::ImQueryAll);
385 }
386}
387
388QWaylandTextInputMethod *QWaylandInputMethodContext::textInputMethod() const
389{
390 return m_display->defaultInputDevice() ? m_display->defaultInputDevice()->textInputMethod() : nullptr;
391}
392
393} // QtWaylandClient
394
395QT_END_NAMESPACE
396
397#include "moc_qwaylandinputmethodcontext_p.cpp"
398

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