Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qohosinputmethodproxy.cpp
Go to the documentation of this file.
1// Copyright (C) 2025 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#include <QtCore/private/qohoslogger_p.h>
6#include <QtCore/private/qstringconverter_p.h>
7#include <algorithm>
8#include <codecvt>
9#include <locale>
10#include <qarkui/qarkuiutils.h>
11#include <qohosplugincore.h>
12#include <qohosutils.h>
13
15
16namespace {
17
18std::string convertUtf16ToUtf8(const std::u16string &utf16String)
19{
20#ifdef __cpp_lib_string_resize_and_overwrite
21 std::string ut8Str;
22 // In the worst case, we need atleast 3 bytes when converting from utf16
23 // to utf8
24 const auto maxBytes = utf16String.size() * 3;
25 utf8Str.resize_and_overwrite(maxBytes, [&] (char *dst, size_t) noexcept {
26 return QUtf8::convertFromUnicode(dst, QStringView{utf16String}) - dst;
27 });
28 return utf8Str;
29#else
30QT_WARNING_PUSH
31QT_WARNING_DISABLE_DEPRECATED
32 return std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t>{}.to_bytes(utf16String);
33QT_WARNING_POP
34#endif
35}
36
38{
39 return std::shared_ptr<::InputMethod_TextEditorProxy>(
40 QArkUi::callArkUiOrFailOnNullResult(
41 Q_OHOS_NAMED_FUNC(::OH_TextEditorProxy_Create)),
42 [](::InputMethod_TextEditorProxy *textEditorProxy) {
43 QArkUi::callArkUi(
44 Q_OHOS_NAMED_FUNC(::OH_TextEditorProxy_Destroy),
45 textEditorProxy);
46 });
47}
48
50 bool showKeyboard, ::InputMethod_RequestKeyboardReason requestKeyboardReason)
51{
52 return std::shared_ptr<::InputMethod_AttachOptions>(
53 QArkUi::callArkUiOrFailOnNullResult(
54 Q_OHOS_NAMED_FUNC(::OH_AttachOptions_CreateWithRequestKeyboardReason),
55 showKeyboard, requestKeyboardReason),
56 [](::InputMethod_AttachOptions *attachOptions) {
57 QArkUi::callArkUi(
58 Q_OHOS_NAMED_FUNC(::OH_AttachOptions_Destroy),
59 attachOptions);
60 });
61}
62
64 std::shared_ptr<::InputMethod_TextEditorProxy> textEditorProxy,
65 std::shared_ptr<::InputMethod_AttachOptions> attachOptions)
66{
67 ::InputMethod_InputMethodProxy *inputMethodProxyPtr = nullptr;
68 ::InputMethod_ErrorCode errcode = QArkUi::callArkUi(
69 Q_OHOS_NAMED_FUNC(::OH_InputMethodController_Attach),
70 textEditorProxy.get(), attachOptions.get(), &inputMethodProxyPtr);
71 if (errcode != ::InputMethod_ErrorCode::IME_ERR_OK) {
72 qOhosPrintfError("%s: OH_InputMethodController_Attach failed!", Q_FUNC_INFO);
73 return {};
74 }
75
76 return std::shared_ptr<::InputMethod_InputMethodProxy>(
77 inputMethodProxyPtr, [](::InputMethod_InputMethodProxy *proxy) {
78 QArkUi::callArkUiOrFailOnErrorResult(
79 Q_OHOS_NAMED_FUNC(::OH_InputMethodController_Detach), proxy);
80 });
81}
82
84 double x, double y, double width, double height)
85{
86 return std::shared_ptr<::InputMethod_CursorInfo>(
87 QArkUi::callArkUiOrFailOnNullResult(
88 Q_OHOS_NAMED_FUNC(::OH_CursorInfo_Create),
89 x, y, width, height),
90 [](::InputMethod_CursorInfo *cursorInfo) {
91 QArkUi::callArkUi(
92 Q_OHOS_NAMED_FUNC(::OH_CursorInfo_Destroy), cursorInfo);
93 });
94}
95
96template<typename CallbackResult, typename... CallbackArgs>
98template<
99 typename CallbackResult,
100 typename... CallbackArgs,
101 ::InputMethod_ErrorCode rawSetCallbackFunc(::InputMethod_TextEditorProxy *, TextEditorProxyCallbackFunc<CallbackResult, CallbackArgs...>)>
103 ::InputMethod_TextEditorProxy *textEditorProxy,
104 QOhosNamedFunc<::InputMethod_ErrorCode(*)(::InputMethod_TextEditorProxy *, TextEditorProxyCallbackFunc<CallbackResult, CallbackArgs...>), rawSetCallbackFunc> setCallbackFunc,
105 std::function<CallbackResult(CallbackArgs...)> callback)
106{
107 static std::map<::InputMethod_TextEditorProxy *, std::shared_ptr<std::function<CallbackResult(CallbackArgs...)>>> callbacksMap;
108
109 bool callbackRegistered;
110 std::tie(std::ignore, callbackRegistered) = callbacksMap.emplace(
111 textEditorProxy,
112 std::make_shared<std::function<CallbackResult(CallbackArgs...)>>(std::move(callback)));
113
114 if (!callbackRegistered)
115 qOhosReportFatalErrorAndAbort(Q_FUNC_INFO, "encountered duplicate callback registration for %p", textEditorProxy);
116
117 QArkUi::callArkUiOrFailOnErrorResult(
118 setCallbackFunc, textEditorProxy,
119 [](::InputMethod_TextEditorProxy *textEditorProxy, CallbackArgs ...callbackArgs) {
120 auto callbackIter = callbacksMap.find(textEditorProxy);
121 if (callbackIter != callbacksMap.end()) {
122 auto sharedCallback = callbackIter->second;
123 return (*sharedCallback)(callbackArgs...);
124 } else {
125 return CallbackResult();
126 }
127 });
128
129 return QtOhos::makeDestroyNotifier(
130 [textEditorProxy]() {
131 callbacksMap.erase(textEditorProxy);
132 });
133}
134
135template<
136 typename CallbackResult,
137 typename... CallbackArgs,
138 ::InputMethod_ErrorCode rawSetCallbackFunc(::InputMethod_TextEditorProxy *, TextEditorProxyCallbackFunc<CallbackResult, CallbackArgs...>),
139 typename Callback>
141 ::InputMethod_TextEditorProxy *textEditorProxy,
142 QOhosNamedFunc<::InputMethod_ErrorCode(*)(::InputMethod_TextEditorProxy *, TextEditorProxyCallbackFunc<CallbackResult, CallbackArgs...>), rawSetCallbackFunc> setCallbackFunc,
143 Callback callback)
144{
145 static_assert(std::is_assignable<std::function<CallbackResult(CallbackArgs...)>, Callback>::value, "");
146 return registerTextEditorProxyCallbackImpl<CallbackResult>(
147 textEditorProxy, setCallbackFunc, std::function<CallbackResult(CallbackArgs...)>(std::move(callback)));
148}
149
150template<typename... CallbackArgs>
152 std::weak_ptr<QOhosInputMethodProxy::ClientCallbacks> weakClientCallbacks,
153 void (QOhosInputMethodProxy::ClientCallbacks::*callbackMemPtr)(CallbackArgs...),
154 CallbackArgs ...args)
155{
156 QtOhos::invokeInQtThread(
157 [weakClientCallbacks, callbackMemPtr, args...]() {
158 auto clientCallbacks = weakClientCallbacks.lock();
159 if (clientCallbacks)
160 (clientCallbacks.get()->*callbackMemPtr)(args...);
161 });
162}
163
164}
165
166std::shared_ptr<void> QOhosInputMethodProxy::registerCallbacks(
167 std::shared_ptr<::InputMethod_TextEditorProxy> textEditorProxy,
168 std::shared_ptr<JsScopeData::JsTextEditorProxyData> textEditorProxyData,
169 std::shared_ptr<QOhosMutexProtectedValue<TextAroundCursor>> textAroundCursor,
170 std::weak_ptr<QOhosInputMethodProxy::ClientCallbacks> weakClientCallbacks)
171{
172 std::vector<std::shared_ptr<void>> registrationHandles;
173
174 registrationHandles.push_back(
175 registerTextEditorProxyCallback(
176 textEditorProxy.get(), Q_OHOS_NAMED_FUNC(::OH_TextEditorProxy_SetGetTextConfigFunc),
177 [](::InputMethod_TextConfig *config) {
178 QArkUi::callArkUiOrFailOnErrorResult(
179 Q_OHOS_NAMED_FUNC(::OH_TextConfig_SetPreviewTextSupport),
180 config, true);
181 }));
182
183 registrationHandles.push_back(
184 registerTextEditorProxyCallback(
185 textEditorProxy.get(), Q_OHOS_NAMED_FUNC(::OH_TextEditorProxy_SetInsertTextFunc),
186 [weakClientCallbacks](const char16_t *text, size_t length) {
187 std::u16string utf16Text(text, length);
188 callInQtThread(
189 weakClientCallbacks, &QOhosInputMethodProxy::ClientCallbacks::onInsertText,
190 convertUtf16ToUtf8(utf16Text));
191 }));
192
193 registrationHandles.push_back(
194 registerTextEditorProxyCallback(
195 textEditorProxy.get(), Q_OHOS_NAMED_FUNC(::OH_TextEditorProxy_SetDeleteForwardFunc),
196 [weakClientCallbacks](std::int32_t length) {
197 callInQtThread(
198 weakClientCallbacks, &QOhosInputMethodProxy::ClientCallbacks::onDeleteForward,
199 length);
200 }));
201
202 registrationHandles.push_back(
203 registerTextEditorProxyCallback(
204 textEditorProxy.get(), Q_OHOS_NAMED_FUNC(::OH_TextEditorProxy_SetDeleteBackwardFunc),
205 [weakClientCallbacks](std::int32_t length) {
206 callInQtThread(
207 weakClientCallbacks, &QOhosInputMethodProxy::ClientCallbacks::onDeleteBackward,
208 length);
209 }));
210
211 registrationHandles.push_back(
212 registerTextEditorProxyCallback(
213 textEditorProxy.get(), Q_OHOS_NAMED_FUNC(::OH_TextEditorProxy_SetSendKeyboardStatusFunc),
214 [weakClientCallbacks, weakTextEditorProxyData = QtOhos::makeWeakPtr(textEditorProxyData)](::InputMethod_KeyboardStatus keyboardStatus) {
215 auto sharedTextEditorProxyData = weakTextEditorProxyData.lock();
216 if (sharedTextEditorProxyData)
217 sharedTextEditorProxyData->textInputShown = keyboardStatus == ::IME_KEYBOARD_STATUS_SHOW;
218
219 callInQtThread(
220 weakClientCallbacks, &QOhosInputMethodProxy::ClientCallbacks::onSendKeyboardStatus,
221 keyboardStatus);
222 }));
223
224 registrationHandles.push_back(
225 registerTextEditorProxyCallback(
226 textEditorProxy.get(), Q_OHOS_NAMED_FUNC(::OH_TextEditorProxy_SetSendEnterKeyFunc),
227 [weakClientCallbacks](::InputMethod_EnterKeyType enterKeyType) {
228 callInQtThread(
229 weakClientCallbacks, &QOhosInputMethodProxy::ClientCallbacks::onSendEnterKey,
230 enterKeyType);
231 }));
232
233 registrationHandles.push_back(
234 registerTextEditorProxyCallback(
235 textEditorProxy.get(), Q_OHOS_NAMED_FUNC(::OH_TextEditorProxy_SetMoveCursorFunc),
236 [weakClientCallbacks](::InputMethod_Direction direction) {
237 callInQtThread(
238 weakClientCallbacks, &QOhosInputMethodProxy::ClientCallbacks::onMoveCursor,
239 direction);
240 }));
241
242 registrationHandles.push_back(
243 registerTextEditorProxyCallback(
244 textEditorProxy.get(), Q_OHOS_NAMED_FUNC(::OH_TextEditorProxy_SetHandleSetSelectionFunc),
245 [](auto...) {
246 }));
247
248 registrationHandles.push_back(
249 registerTextEditorProxyCallback(
250 textEditorProxy.get(), Q_OHOS_NAMED_FUNC(::OH_TextEditorProxy_SetHandleExtendActionFunc),
251 [](auto...) {
252 }));
253
254 auto makeGetTextOfCursorCallback = [weakTextAroundCursor = QtOhos::makeWeakPtr(textAroundCursor)](
255 std::function<std::u16string(const TextAroundCursor &, std::size_t maxLength)> selectFromTextAroundCursorFunc) {
256 return [weakTextAroundCursor, selectFromTextAroundCursorFunc = std::move(selectFromTextAroundCursorFunc)](
257 int32_t maxLength, char16_t *outBuffer, std::size_t *outLength) {
258 auto protectedTextAroundCursor = weakTextAroundCursor.lock();
259 if (!protectedTextAroundCursor) {
260 qOhosPrintfDebug("%s: cannot get text of cursor", Q_FUNC_INFO);
261 *outLength = 0;
262 return;
263 }
264
265 const auto selectedText = protectedTextAroundCursor->evalWithValue(
266 [maxLength, &selectFromTextAroundCursorFunc](const TextAroundCursor &textAroundCursor) {
267 return selectFromTextAroundCursorFunc(textAroundCursor, static_cast<std::size_t>(maxLength));
268 });
269 *outLength = selectedText.copy(outBuffer, selectedText.size());
270 };
271 };
272
273 registrationHandles.push_back(
274 registerTextEditorProxyCallback(
275 textEditorProxy.get(), Q_OHOS_NAMED_FUNC(::OH_TextEditorProxy_SetGetLeftTextOfCursorFunc),
276 makeGetTextOfCursorCallback(
277 [](const TextAroundCursor &textAroundCursor, std::size_t maxLength) {
278 const auto &leftText = textAroundCursor.leftText;
279 return leftText.substr(leftText.size() - std::min(maxLength, leftText.size()));
280 })));
281
282 registrationHandles.push_back(
283 registerTextEditorProxyCallback(
284 textEditorProxy.get(), Q_OHOS_NAMED_FUNC(::OH_TextEditorProxy_SetGetRightTextOfCursorFunc),
285 makeGetTextOfCursorCallback(
286 [](const TextAroundCursor &textAroundCursor, std::size_t maxLength) {
287 const auto &rightText = textAroundCursor.rightText;
288 return rightText.substr(0, std::min(maxLength, rightText.size()));
289 })));
290
291 QArkUi::callArkUiOrFailOnErrorResult(
292 Q_OHOS_NAMED_FUNC(::OH_TextEditorProxy_SetGetTextIndexAtCursorFunc),
293 textEditorProxy.get(),
294 [](::InputMethod_TextEditorProxy *) {
295 return 0;
296 });
297
298 QArkUi::callArkUiOrFailOnErrorResult(
299 Q_OHOS_NAMED_FUNC(::OH_TextEditorProxy_SetReceivePrivateCommandFunc),
300 textEditorProxy.get(),
301 [](::InputMethod_TextEditorProxy *, ::InputMethod_PrivateCommand **, unsigned long) {
302 return 0;
303 });
304
305 registrationHandles.push_back(
306 registerTextEditorProxyCallback(
307 textEditorProxy.get(),
308 Q_OHOS_NAMED_FUNC(::OH_TextEditorProxy_SetSetPreviewTextFunc),
309 [weakClientCallbacks](const char16_t *text, std::size_t length, std::int32_t, std::int32_t) {
310 std::u16string utf16Text(text, length);
311 callInQtThread(
312 weakClientCallbacks, &QOhosInputMethodProxy::ClientCallbacks::onInsertPreviewText,
313 convertUtf16ToUtf8(utf16Text));
314 return 0;
315 }));
316
317 registrationHandles.push_back(
318 registerTextEditorProxyCallback(
319 textEditorProxy.get(), Q_OHOS_NAMED_FUNC(::OH_TextEditorProxy_SetFinishTextPreviewFunc),
320 [weakClientCallbacks]() {
321 callInQtThread(
322 weakClientCallbacks, &QOhosInputMethodProxy::ClientCallbacks::onFinishPreviewText);
323 }));
324
325 return QtOhos::moveToSharedPtr(std::move(registrationHandles));
326}
327
328QOhosInputMethodProxy::QOhosInputMethodProxy(
329 std::shared_ptr<ClientCallbacks> clientCallbacks, ::InputMethod_RequestKeyboardReason requestKeyboardReason)
330 : m_clientCallbacks(clientCallbacks)
331 , m_textAroundCursor(std::make_shared<QOhosMutexProtectedValue<TextAroundCursor>>())
332{
333 auto weakClientCallbacks = QtOhos::makeWeakPtr(clientCallbacks);
334 m_jsScopeData = QtOhos::evalInJsThread(
335 [&](auto &) -> std::shared_ptr<JsScopeData> {
336 auto textEditorProxyData = std::make_shared<JsScopeData::JsTextEditorProxyData>();
337 auto textEditorProxy = makeTextEditorProxy();
338 auto callbacksRegistrationHandle = registerCallbacks(
339 textEditorProxy, textEditorProxyData, m_textAroundCursor, weakClientCallbacks);
340
341 auto showKeyboard = true;
342 auto attachOptions = makeAttachOptions(showKeyboard, requestKeyboardReason);
343
344 auto inputMethodProxy = tryMakeInputMethodProxy(textEditorProxy, attachOptions);
345 if (!inputMethodProxy) {
346 qOhosPrintfError("%s: inputMethodProxy is nullptr!", Q_FUNC_INFO);
347 return nullptr;
348 }
349
350 return QtOhos::makeProxyWithJsThreadDeleter(
351 QtOhos::moveToSharedPtr(
352 JsScopeData {
353 .textEditorProxy = textEditorProxy,
354 .textEditorProxyData = textEditorProxyData,
355 .inputMethodProxy = inputMethodProxy,
356 .callbacksRegistrationHandle = callbacksRegistrationHandle,
357 }));
358 },
359 Q_FUNC_INFO);
360}
361
362bool QOhosInputMethodProxy::hasAttachedSuccessfully()
363{
364 return m_jsScopeData != nullptr;
365}
366
367void QOhosInputMethodProxy::showTextInput(::InputMethod_RequestKeyboardReason requestKeyboardReason)
368{
369 if (!hasAttachedSuccessfully()) {
370 qOhosPrintfError("%s: operation aborted, IMC not attached.", Q_FUNC_INFO);
371 return;
372 }
373
375 [&](QtOhos::JsState &) {
376 if (m_jsScopeData->textEditorProxyData->textInputShown) {
377 qOhosPrintfDebug("%s: text input already shown, skip showing it again", Q_FUNC_INFO);
378 return;
379 }
380
381 auto showKeyboard = true;
382 auto attachOptions = makeAttachOptions(showKeyboard, requestKeyboardReason);
383
384 ::InputMethod_ErrorCode errcode = QArkUi::callArkUi(
385 Q_OHOS_NAMED_FUNC(::OH_InputMethodProxy_ShowTextInput),
386 m_jsScopeData->inputMethodProxy.get(),
387 attachOptions.get());
388 m_jsScopeData->textEditorProxyData->textInputShown = errcode == ::InputMethod_ErrorCode::IME_ERR_OK;
389 if (errcode != ::InputMethod_ErrorCode::IME_ERR_OK)
390 qOhosPrintfError("%s: OH_InputMethodProxy_ShowTextInput failed!", Q_FUNC_INFO);
391 },
392 Q_FUNC_INFO);
393}
394
395void QOhosInputMethodProxy::notifyConfigurationChange(
396 ::InputMethod_EnterKeyType enterKeyType, ::InputMethod_TextInputType textInputType)
397{
398 if (!hasAttachedSuccessfully()) {
399 qOhosPrintfError("%s: operation aborted, IMC not attached.", Q_FUNC_INFO);
400 return;
401 }
402
404 [&](QtOhos::JsState &) {
405 ::InputMethod_ErrorCode errcode = QArkUi::callArkUi(
406 Q_OHOS_NAMED_FUNC(::OH_InputMethodProxy_NotifyConfigurationChange),
407 m_jsScopeData->inputMethodProxy.get(),
408 enterKeyType, textInputType);
409 if (errcode != ::InputMethod_ErrorCode::IME_ERR_OK)
410 qOhosPrintfError("%s: OH_InputMethodProxy_NotifyConfigurationChange failed!", Q_FUNC_INFO);
411 },
412 Q_FUNC_INFO);
413}
414
415void QOhosInputMethodProxy::notifyCursorUpdate(const QRectF &cursorRect)
416{
417 if (!hasAttachedSuccessfully()) {
418 qOhosPrintfError("%s: operation aborted, IMC not attached.", Q_FUNC_INFO);
419 return;
420 }
421
422 double x = cursorRect.x();
423 double y = cursorRect.y();
424 double width = cursorRect.width();
425 double height = cursorRect.height();
426
428 [&](QtOhos::JsState &) {
429 auto ohCursorInfo = makeCursorInfo(x, y, width, height);
430
431 ::InputMethod_ErrorCode errcode = QArkUi::callArkUi(
432 Q_OHOS_NAMED_FUNC(::OH_InputMethodProxy_NotifyCursorUpdate),
433 m_jsScopeData->inputMethodProxy.get(), ohCursorInfo.get());
434 if (errcode != ::InputMethod_ErrorCode::IME_ERR_OK)
435 qOhosPrintfError("%s: OH_InputMethodProxy_NotifyCursorUpdate failed!", Q_FUNC_INFO);
436 },
437 Q_FUNC_INFO);
438}
439
440void QOhosInputMethodProxy::setTextAroundCursor(std::u16string leftText, std::u16string rightText)
441{
442 if (!hasAttachedSuccessfully()) {
443 qOhosPrintfError("%s: operation aborted, IMC not attached.", Q_FUNC_INFO);
444 return;
445 }
446
447 m_textAroundCursor->processValue(
448 [&](TextAroundCursor &textAroundCursor) {
449 textAroundCursor.leftText = std::move(leftText);
450 textAroundCursor.rightText = std::move(rightText);
451 });
452}
453
454QOhosInputMethodProxy::ClientCallbacks::ClientCallbacks() = default;
455
456QOhosInputMethodProxy::ClientCallbacks::~ClientCallbacks() = default;
457
458QT_END_NAMESPACE
Combined button and popup list for selecting options.
std::shared_ptr< void > registerTextEditorProxyCallback(::InputMethod_TextEditorProxy *textEditorProxy, QOhosNamedFunc<::InputMethod_ErrorCode(*)(::InputMethod_TextEditorProxy *, TextEditorProxyCallbackFunc< CallbackResult, CallbackArgs... >), rawSetCallbackFunc > setCallbackFunc, Callback callback)
void callInQtThread(std::weak_ptr< QOhosInputMethodProxy::ClientCallbacks > weakClientCallbacks, void(QOhosInputMethodProxy::ClientCallbacks::*callbackMemPtr)(CallbackArgs...), CallbackArgs ...args)
std::shared_ptr<::InputMethod_AttachOptions > makeAttachOptions(bool showKeyboard, ::InputMethod_RequestKeyboardReason requestKeyboardReason)
std::shared_ptr<::InputMethod_CursorInfo > makeCursorInfo(double x, double y, double width, double height)
std::shared_ptr< void > registerTextEditorProxyCallbackImpl(::InputMethod_TextEditorProxy *textEditorProxy, QOhosNamedFunc<::InputMethod_ErrorCode(*)(::InputMethod_TextEditorProxy *, TextEditorProxyCallbackFunc< CallbackResult, CallbackArgs... >), rawSetCallbackFunc > setCallbackFunc, std::function< CallbackResult(CallbackArgs...)> callback)
CallbackResult(::InputMethod_TextEditorProxy *, CallbackArgs...) TextEditorProxyCallbackFunc
std::string convertUtf16ToUtf8(const std::u16string &utf16String)
std::shared_ptr<::InputMethod_TextEditorProxy > makeTextEditorProxy()
std::shared_ptr<::InputMethod_InputMethodProxy > tryMakeInputMethodProxy(std::shared_ptr<::InputMethod_TextEditorProxy > textEditorProxy, std::shared_ptr<::InputMethod_AttachOptions > attachOptions)
void runInJsThreadAndWait(const std::function< void(JsState &)> &task, std::string callerContextName={})