1// Copyright (C) 2016 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4
5#include "qwaylandinputcontext_p.h"
6
7#include <QLoggingCategory>
8#include <QtGui/QGuiApplication>
9#include <QtGui/QTextCharFormat>
10#include <QtGui/QWindow>
11#include <QtCore/QVarLengthArray>
12
13#include "qwaylanddisplay_p.h"
14#include "qwaylandinputdevice_p.h"
15#include "qwaylandwindow_p.h"
16
17#if QT_CONFIG(xkbcommon)
18#include <locale.h>
19#endif
20
21QT_BEGIN_NAMESPACE
22
23Q_LOGGING_CATEGORY(qLcQpaInputMethods, "qt.qpa.input.methods")
24
25namespace QtWaylandClient {
26
27QWaylandInputContext::QWaylandInputContext(QWaylandDisplay *display)
28 : mDisplay(display)
29{
30}
31
32QWaylandInputContext::~QWaylandInputContext()
33{
34}
35
36bool QWaylandInputContext::isValid() const
37{
38#if QT_WAYLAND_TEXT_INPUT_V4_WIP
39 return mDisplay->textInputManagerv2() != nullptr || mDisplay->textInputManagerv1() != nullptr || mDisplay->textInputManagerv4() != nullptr;
40#else // QT_WAYLAND_TEXT_INPUT_V4_WIP
41 return mDisplay->textInputManagerv2() != nullptr || mDisplay->textInputManagerv1() != nullptr;
42#endif // QT_WAYLAND_TEXT_INPUT_V4_WIP
43}
44
45void QWaylandInputContext::reset()
46{
47 qCDebug(qLcQpaInputMethods) << Q_FUNC_INFO;
48#if QT_CONFIG(xkbcommon)
49 if (m_composeState)
50 xkb_compose_state_reset(state: m_composeState);
51#endif
52
53 QPlatformInputContext::reset();
54
55 if (!textInput())
56 return;
57
58 textInput()->reset();
59}
60
61void QWaylandInputContext::commit()
62{
63 qCDebug(qLcQpaInputMethods) << Q_FUNC_INFO;
64
65 if (!textInput())
66 return;
67
68 textInput()->commit();
69}
70
71static ::wl_surface *surfaceForWindow(QWindow *window)
72{
73 if (!window || !window->handle())
74 return nullptr;
75
76 auto *waylandWindow = static_cast<QWaylandWindow *>(window->handle());
77 return waylandWindow->wlSurface();
78}
79
80void QWaylandInputContext::update(Qt::InputMethodQueries queries)
81{
82 qCDebug(qLcQpaInputMethods) << Q_FUNC_INFO << queries;
83
84 if (!QGuiApplication::focusObject() || !textInput())
85 return;
86
87 auto *currentSurface = surfaceForWindow(window: mCurrentWindow);
88
89 if (currentSurface && !inputMethodAccepted()) {
90 textInput()->disableSurface(surface: currentSurface);
91 mCurrentWindow.clear();
92 } else if (!currentSurface && inputMethodAccepted()) {
93 QWindow *window = QGuiApplication::focusWindow();
94 if (auto *focusSurface = surfaceForWindow(window)) {
95 textInput()->enableSurface(surface: focusSurface);
96 mCurrentWindow = window;
97 }
98 }
99
100 textInput()->updateState(queries, flags: QWaylandTextInputInterface::update_state_change);
101}
102
103void QWaylandInputContext::invokeAction(QInputMethod::Action action, int cursorPostion)
104{
105 if (!textInput())
106 return;
107
108 if (action == QInputMethod::Click)
109 textInput()->setCursorInsidePreedit(cursorPostion);
110}
111
112void QWaylandInputContext::showInputPanel()
113{
114 qCDebug(qLcQpaInputMethods) << Q_FUNC_INFO;
115
116 if (!textInput())
117 return;
118
119 textInput()->showInputPanel();
120}
121
122void QWaylandInputContext::hideInputPanel()
123{
124 qCDebug(qLcQpaInputMethods) << Q_FUNC_INFO;
125
126 if (!textInput())
127 return;
128
129 textInput()->hideInputPanel();
130}
131
132bool QWaylandInputContext::isInputPanelVisible() const
133{
134 qCDebug(qLcQpaInputMethods) << Q_FUNC_INFO;
135
136 if (!textInput())
137 return QPlatformInputContext::isInputPanelVisible();
138
139 return textInput()->isInputPanelVisible();
140}
141
142QRectF QWaylandInputContext::keyboardRect() const
143{
144 qCDebug(qLcQpaInputMethods) << Q_FUNC_INFO;
145
146 if (!textInput())
147 return QPlatformInputContext::keyboardRect();
148
149 return textInput()->keyboardRect();
150}
151
152QLocale QWaylandInputContext::locale() const
153{
154 qCDebug(qLcQpaInputMethods) << Q_FUNC_INFO;
155
156 if (!textInput())
157 return QPlatformInputContext::locale();
158
159 return textInput()->locale();
160}
161
162Qt::LayoutDirection QWaylandInputContext::inputDirection() const
163{
164 qCDebug(qLcQpaInputMethods) << Q_FUNC_INFO;
165
166 if (!textInput())
167 return QPlatformInputContext::inputDirection();
168
169 return textInput()->inputDirection();
170}
171
172void QWaylandInputContext::setFocusObject(QObject *object)
173{
174 qCDebug(qLcQpaInputMethods) << Q_FUNC_INFO;
175#if QT_CONFIG(xkbcommon)
176 m_focusObject = object;
177#else
178 Q_UNUSED(object);
179#endif
180
181 if (!textInput())
182 return;
183
184 QWindow *window = QGuiApplication::focusWindow();
185
186 if (mCurrentWindow && mCurrentWindow->handle()) {
187 if (mCurrentWindow.data() != window || !inputMethodAccepted()) {
188 auto *surface = static_cast<QWaylandWindow *>(mCurrentWindow->handle())->wlSurface();
189 if (surface)
190 textInput()->disableSurface(surface);
191 mCurrentWindow.clear();
192 }
193 }
194
195 if (window && window->handle() && inputMethodAccepted()) {
196 if (mCurrentWindow.data() != window) {
197 auto *surface = static_cast<QWaylandWindow *>(window->handle())->wlSurface();
198 if (surface) {
199 textInput()->enableSurface(surface);
200 mCurrentWindow = window;
201 }
202 }
203 textInput()->updateState(queries: Qt::ImQueryAll, flags: QWaylandTextInputInterface::update_state_enter);
204 }
205}
206
207QWaylandTextInputInterface *QWaylandInputContext::textInput() const
208{
209 return mDisplay->defaultInputDevice() ? mDisplay->defaultInputDevice()->textInput() : nullptr;
210}
211
212#if QT_CONFIG(xkbcommon)
213
214void QWaylandInputContext::ensureInitialized()
215{
216 if (m_initialized)
217 return;
218
219 if (!m_XkbContext) {
220 qCWarning(qLcQpaInputMethods) << "error: xkb context has not been set on" << metaObject()->className();
221 return;
222 }
223
224 m_initialized = true;
225 const char *const locale = setlocale(LC_CTYPE, locale: nullptr);
226 qCDebug(qLcQpaInputMethods) << "detected locale (LC_CTYPE):" << locale;
227
228 m_composeTable = xkb_compose_table_new_from_locale(context: m_XkbContext, locale, flags: XKB_COMPOSE_COMPILE_NO_FLAGS);
229 if (m_composeTable)
230 m_composeState = xkb_compose_state_new(table: m_composeTable, flags: XKB_COMPOSE_STATE_NO_FLAGS);
231
232 if (!m_composeTable) {
233 qCWarning(qLcQpaInputMethods, "failed to create compose table");
234 return;
235 }
236 if (!m_composeState) {
237 qCWarning(qLcQpaInputMethods, "failed to create compose state");
238 return;
239 }
240}
241
242bool QWaylandInputContext::filterEvent(const QEvent *event)
243{
244 auto keyEvent = static_cast<const QKeyEvent *>(event);
245 if (keyEvent->type() != QEvent::KeyPress)
246 return false;
247
248 if (!inputMethodAccepted())
249 return false;
250
251 // lazy initialization - we don't want to do this on an app startup
252 ensureInitialized();
253
254 if (!m_composeTable || !m_composeState)
255 return false;
256
257 xkb_compose_state_feed(state: m_composeState, keysym: keyEvent->nativeVirtualKey());
258
259 switch (xkb_compose_state_get_status(state: m_composeState)) {
260 case XKB_COMPOSE_COMPOSING:
261 return true;
262 case XKB_COMPOSE_CANCELLED:
263 reset();
264 return false;
265 case XKB_COMPOSE_COMPOSED:
266 {
267 const int size = xkb_compose_state_get_utf8(state: m_composeState, buffer: nullptr, size: 0);
268 QVarLengthArray<char, 32> buffer(size + 1);
269 xkb_compose_state_get_utf8(state: m_composeState, buffer: buffer.data(), size: buffer.size());
270 QString composedText = QString::fromUtf8(utf8: buffer.constData());
271
272 QInputMethodEvent event;
273 event.setCommitString(commitString: composedText);
274
275 if (!m_focusObject && qApp)
276 m_focusObject = qApp->focusObject();
277
278 if (m_focusObject)
279 QCoreApplication::sendEvent(receiver: m_focusObject, event: &event);
280 else
281 qCWarning(qLcQpaInputMethods, "no focus object");
282
283 reset();
284 return true;
285 }
286 case XKB_COMPOSE_NOTHING:
287 return false;
288 default:
289 Q_UNREACHABLE_RETURN(false);
290 }
291}
292
293#endif
294
295}
296
297QT_END_NAMESPACE
298
299#include "moc_qwaylandinputcontext_p.cpp"
300

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