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
qohosappcontext.cpp
Go to the documentation of this file.
1// Copyright (C) 2026 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
6#include <QtHarmonyExtras/private/qohosabilitycontext_p.h>
7#include <QtHarmonyExtras/private/qohosappbundleinfo_p.h>
8#include <QtHarmonyExtras/private/qohosjsenv_p.h>
9#include <QtHarmonyExtras/private/qohoswantinfo_p.h>
10#include <QtHarmonyExtras/private/qohoswantutils_p.h>
11
12#include <QtCore/private/qcore_ohos_p.h>
13#include <QtCore/private/qnapi_p.h>
14#include <QtCore/private/qohoscommon_p.h>
15#include <QtCore/private/qohosjstools_p.h>
16#include <QtCore/private/qohoslogger_p.h>
17
18#include <algorithm>
19#include <chrono>
20#include <csignal>
21#include <cstdint>
22#include <cstdlib>
23#include <functional>
24#include <iterator>
25#include <memory>
26#include <optional>
27#include <string>
28#include <thread>
29#include <unordered_map>
30#include <vector>
31
32#include <unistd.h>
33
34QT_BEGIN_NAMESPACE
35
36namespace QtHarmonyExtras {
37
38using namespace Private;
39
40namespace {
41
42template<typename T>
43using QOhosSupplier = std::function<T()>;
44
45int getCurrentApplicationVersionCode()
46{
47 return QOhosJsThreadGateway::eval(
48 [](QOhosJsState &jsState) {
49 auto applicationInfoFlag = jsState.eval<QNapi::Number>(
50 "@ohos.bundle.bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_APPLICATION");
51 auto bundleInfo = jsState.eval<QNapi::Object>(
52 "@ohos.bundle.bundleManager.getBundleInfoForSelfSync(*)", {applicationInfoFlag});
53 int versionCode = bundleInfo.get<QNapi::Number>("versionCode");
54
55 return versionCode;
56 },
57 Q_FUNC_INFO);
58}
59
60Q_NORETURN void killCurrentProcess()
61{
62 ::kill(getpid(), SIGKILL);
63 std::abort();
64}
65
66std::optional<std::uint32_t> tryGetCodeFromJsBusinessError(const Napi::Error &error)
67{
68 if (!error.Value().IsObject())
69 return std::nullopt;
70
71 auto errorObject = QNapi::checkedCast<QNapi::Object>(error.Value());
72 auto optErrorCode = QNapi::getOptionalPropOrEmpty<QNapi::Number>(errorObject, "code");
73
74 return !optErrorCode.IsEmpty()
75 ? std::make_optional(optErrorCode.Uint32Value())
76 : std::nullopt;
77}
78
79Q_NORETURN void restartAppImpl(std::optional<QJsonObject> want)
80{
81 QOhosJsThreadGateway::runAndWait(
82 [&](QOhosJsState &jsState) {
83 auto napiWant = want.has_value()
84 ? QNapi::checkedCast<QNapi::Object>(QOhosJsEnv::toNapiValue(jsState.env(), want.value()))
85 : jsState.appLaunchWant();
86
87 constexpr auto sleepTimeBeforeRetry = std::chrono::seconds(3);
88
89 unsigned remainingTries = 3;
90
91 while (true) {
92 --remainingTries;
93
94 qOhosPrintfInfo(
95 "%s: calling restartApp() using Want: %s",
96 Q_FUNC_INFO, QNapi::toJsonString(napiWant).c_str());
97
98 auto optQAbility = jsState.defaultQAbility();
99 if (!optQAbility.has_value())
100 qOhosReportFatalErrorAndAbort("%s: no default UIAbility available to restart the app", Q_FUNC_INFO);
101
102 try {
103 optQAbility.value().eval(
104 "context.getApplicationContext().restartApp(*)", {napiWant});
105
106 qOhosPrintfWarning("%s: restartApp() call unexpectedly returned, killing self", Q_FUNC_INFO);
107 killCurrentProcess();
108 } catch (const Napi::Error &error) {
109 constexpr std::uint32_t restartTooFrequentlyErrorCode = 16000064;
110
111 auto errorCode = tryGetCodeFromJsBusinessError(error);
112
113 if (errorCode == restartTooFrequentlyErrorCode && remainingTries != 0) {
114 qOhosPrintfWarning(
115 "%s: restartApp() returned with error %u, sleeping before retry",
116 Q_FUNC_INFO, restartTooFrequentlyErrorCode);
117
118 std::this_thread::sleep_for(sleepTimeBeforeRetry);
119 } else {
120 auto errorCodeStr = errorCode.has_value()
121 ? std::to_string(errorCode.value())
122 : "?";
123 qOhosPrintfWarning(
124 "%s: restartApp() returned with error %s, killing self",
125 Q_FUNC_INFO, errorCodeStr.c_str());
126
127 killCurrentProcess();
128 }
129 }
130 }
131 },
132 Q_FUNC_INFO);
133
134 qOhosReportFatalErrorAndAbort("%s: unexpected return from the JS thread call", Q_FUNC_INFO);
135}
136
137struct SerialPortPermissionsState
138{
139 std::unordered_map<std::uint32_t, std::vector<QOhosConsumer<std::shared_ptr<void>>>> m_pendingSerialPortsPermissionRequestsConsumers;
140 std::unordered_map<std::uint32_t, std::weak_ptr<void>> m_grantedSerialPortsPermissionContexts;
141};
142
143std::shared_ptr<SerialPortPermissionsState> serialPortPermissionsState()
144{
145 static auto state = std::make_shared<SerialPortPermissionsState>();
146 return state;
147}
148
149std::optional<std::uint32_t> tryConvertPortNameToSystemPortId(const QString &portName)
150{
151 constexpr const char *serialPortPrefix = "COM";
152 const QString prefix = QLatin1String(serialPortPrefix);
153
154 if (!portName.startsWith(prefix))
155 return {};
156
157 bool parsedOk = false;
158 const uint parsedValue = portName.mid(prefix.length()).toUInt(&parsedOk);
159 if (!parsedOk)
160 return {};
161
162 return static_cast<std::uint32_t>(parsedValue);
163}
164
165bool hasSerialPortAccessRightJsImpl(QOhosJsState &jsState, std::uint32_t serialPortId)
166{
167 try {
168 return jsState.eval<QNapi::Boolean>("@ohos.usbManager.serial.hasSerialRight(*)", {serialPortId});
169 } catch (const Napi::Error &error) {
170 qOhosPrintfError(
171 "%s: hasSerialRight for port %d failed with error: %s",
172 Q_FUNC_INFO, serialPortId, error.what());
173 return false;
174 }
175}
176
177void requestSerialPortAccessRightJsImpl(
178 QOhosJsState &jsState, std::uint32_t serialPortId, QOhosConsumer<bool> resultConsumer)
179{
180 jsState.evalToPromiseOrRejectOnThrow(
181 "@ohos.usbManager.serial.requestSerialRight(*)", {serialPortId})
182 .withContext(std::move(resultConsumer))
183 .onThenWithContext(
184 [](const QOhosCallbackInfo &cbInfo, auto &resultConsumer) {
185 bool granted = cbInfo.getFirstArg<QNapi::Boolean>(Q_FUNC_INFO);
186 resultConsumer(granted);
187 })
188 .onCatchWithContext(
189 [](const QOhosCallbackInfo &cbInfo, auto &resultConsumer) {
190 QtOhos::logJsCallbackError(
191 cbInfo, "@ohos.usbManager.serial.requestSerialRight() failed");
192 resultConsumer(false);
193 });
194}
195
196void cancelSerialPortAccessRightJsImpl(QOhosJsState &jsState, std::uint32_t serialPortId)
197{
198 if (!hasSerialPortAccessRightJsImpl(jsState, serialPortId))
199 return;
200
201 try {
202 jsState.eval("@ohos.usbManager.serial.cancelSerialRight(*)", {serialPortId});
203 } catch (const Napi::Error &error) {
204 qOhosPrintfError(
205 "%s: cancelSerialRight(%u) failed with error (ignoring): %s",
206 Q_FUNC_INFO, serialPortId, error.what());
207 }
208}
209
210void processSerialPortPermissionResponse(std::uint32_t serialPortId, bool granted)
211{
212 auto self = serialPortPermissionsState();
213
214 auto permissionContext = granted
215 ? QtOhos::makeDestroyNotifier(
216 [serialPortId, weakSelf = QtOhos::makeWeakPtr(serialPortPermissionsState())]() {
217 QtOhos::invokeInQtThread(
218 [serialPortId, weakSelf]() {
219 QOhosJsThreadGateway::runAndWait(
220 [&](QOhosJsState &jsState) {
221 cancelSerialPortAccessRightJsImpl(jsState, serialPortId);
222 },
223 Q_FUNC_INFO);
224
225 auto self = weakSelf.lock();
226 if (self)
227 self->m_grantedSerialPortsPermissionContexts.erase(serialPortId);
228 });
229 })
230 : nullptr;
231
232 if (permissionContext)
233 self->m_grantedSerialPortsPermissionContexts[serialPortId] = permissionContext;
234
235 for (const auto &asyncPermissionRequestConsumer : self->m_pendingSerialPortsPermissionRequestsConsumers[serialPortId])
236 asyncPermissionRequestConsumer(permissionContext);
237
238 self->m_pendingSerialPortsPermissionRequestsConsumers.erase(serialPortId);
239}
240
241void requestSerialPortAccessRight(
242 const QString &portName, QObject *resultConsumerQtContext,
243 QOhosConsumer<std::shared_ptr<void>> resultConsumer)
244{
245 auto resultConsumerQtContextRef = QtOhos::makeQThreadSafeRef(resultConsumerQtContext);
246 auto asyncResultConsumer = [resultConsumerQtContextRef, resultConsumer = std::move(resultConsumer)](std::shared_ptr<void> permissionContext) {
247 resultConsumerQtContextRef.visitInQtThreadIfAlive(
248 [resultConsumer = std::move(resultConsumer), permissionContext](auto &resultConsumerQtContext) {
249 QMetaObject::invokeMethod(
250 &resultConsumerQtContext,
251 [resultConsumer = std::move(resultConsumer), permissionContext]() {
252 resultConsumer(permissionContext);
253 },
254 Qt::QueuedConnection);
255 });
256 };
257
258 const auto optSerialPortId = tryConvertPortNameToSystemPortId(portName);
259 if (!optSerialPortId.has_value()) {
260 qOhosPrintfError(
261 "%s: cannot convert serial port name '%s' to port id.",
262 Q_FUNC_INFO, portName.toStdString().c_str());
263
264 asyncResultConsumer(nullptr);
265 return;
266 }
267
268 QtOhos::invokeInQtThread(
269 [serialPortId = optSerialPortId.value(), weakSelf = QtOhos::makeWeakPtr(serialPortPermissionsState()), asyncResultConsumer = std::move(asyncResultConsumer)]() {
270 auto self = weakSelf.lock();
271 if (!self)
272 return;
273
274 auto alreadyGrantedPermissionContextIt =
275 self->m_grantedSerialPortsPermissionContexts.find(serialPortId);
276 auto optAlreadyGrantedPermissionContext =
277 alreadyGrantedPermissionContextIt != self->m_grantedSerialPortsPermissionContexts.end()
278 ? alreadyGrantedPermissionContextIt->second.lock()
279 : nullptr;
280
281 if (optAlreadyGrantedPermissionContext) {
282 asyncResultConsumer(optAlreadyGrantedPermissionContext);
283 return;
284 }
285
286 self->m_pendingSerialPortsPermissionRequestsConsumers[serialPortId].push_back(
287 std::move(asyncResultConsumer));
288
289 if (self->m_pendingSerialPortsPermissionRequestsConsumers[serialPortId].size() == 1) {
290 QOhosJsThreadGateway::invoke(
291 [serialPortId, weakSelf](QOhosJsState &jsState) {
292 requestSerialPortAccessRightJsImpl(
293 jsState,
294 serialPortId,
295 [serialPortId, weakSelf](bool granted) {
296 QtOhos::invokeInQtThread(
297 [serialPortId, weakSelf, granted]() {
298 auto self = weakSelf.lock();
299 if (self)
300 processSerialPortPermissionResponse(serialPortId, granted);
301 });
302 });
303 });
304 }
305 });
306}
307
308class QOhosAppContextImpl : public AppContext
309{
310public:
311 QOhosAppContextImpl();
312
313 bool hasSerialPortAccessRight(const QString &portName) const override;
314 void requestSerialPortAccessRightIfNeeded(
315 const QString &portName, QObject *context,
316 std::function<void(std::shared_ptr<QObject>)> callback) override;
317 std::shared_ptr<BundleInfo> bundleInfo() const override;
318 Q_NORETURN void restartApp(const std::optional<Want> &want) override;
319
320 double fontSizeScale() const override;
321
322private:
323 QOhosSupplier<double> m_fontSizeScaleSupplier;
324};
325
326template<typename T>
327std::shared_ptr<QObject> makeQObjectLifetimeHandleOrNull(std::shared_ptr<T> handle)
328{
329 if (!handle)
330 return {};
331
332 return std::shared_ptr<QObject>(
333 new QObject(),
334 [handle](QObject *ptr) {
335 ptr->deleteLater();
336 });
337}
338
339}
340
341/*!
342 \class QtHarmonyExtras::AppContext
343 \inmodule QtHarmonyExtras
344 \since 5.12.12
345 \brief The AppContext class contains API to manage native application context.
346*/
347
348AppContext::AppContext() = default;
349
350AppContext::~AppContext() = default;
351
352/*!
353 \fn static AppContext *QtHarmonyExtras::AppContext::instance()
354
355 Gets AppContext global instance.
356*/
362
363/*!
364 \fn static bool QtHarmonyExtras::AppContext::isNoUiChildMode()
365
366 Returns \c true if the current process was started as a "No UI" child
367 process (see startNoUiChildProcess()), otherwise returns \c false.
368*/
370{
371 static const bool noUiChildMode = QOhosJsThreadGateway::eval(
372 [](QOhosJsState &jsState) {
373 return !jsState.defaultQAbility();
374 },
376 return noUiChildMode;
377}
378
379/*!
380 \fn static void QtHarmonyExtras::AppContext::startNoUiChildProcess(const QString &libraryName, const QStringList &args)
381
382 Starts "No UI" child process for a given \a libraryName and \a args. Arguments passed to the
383 startNoUiChildProcess() function are forwarded to the child's main() function.
384 See \l {https://developer.huawei.com/consumer/en/doc/harmonyos-references-V5/js-apis-app-ability-childprocessmanager-V5}
385 {Child Process Manager}.
386
387 \code
388 QtHarmonyExtras::AppContext::startNoUiChildProcess(
389 "libapp.so",
390 QStringList{
391 "first arg",
392 "second arg",
393 });
394 \endcode
395*/
408
409/*!
410 \fn static std::shared_ptr<QtHarmonyExtras::WantInfo> QtHarmonyExtras::AppContext::appLaunchWantInfo()
411
412 Returns the Want object that was used to launch initial instance of the application's QAbility.
413*/
418
419/*!
420 \fn static void QtHarmonyExtras::AppContext::restartApp(const std::optional<QtHarmonyExtras::Want> &want)
421
422 Restarts the Application. If \a want is set, the new instance is launched with it; if \a want is
423 empty, the application is restarted with the app launch want.
424
425 The current application will be killed using SIGKILL and a new instance of the application will
426 be launched.
427
428 All abilities and sub-widnows created within this process will be closed.
429
430 The application will be killed ungracefully. This function won't return to the caller.
431
432 The caller must ensure, that the application has system focus when this function is called,
433 otherwise the application will be killed but the new application won't be started. OHOS system
434 treats some system dialogs (for example File Dialog) as separate from the application. If such
435 dialog is open, the application loses the system focus.
436
437 If restartApp is called too frequently, the system call will be throttled to avoid errors.
438
439 \code
440 QtHarmonyExtras::Want requestWant = QtHarmonyExtras::AppContext::getAppLaunchWant();
441 requestWant.parameters["first_parameter"] = "first_parameter_value";
442 requestWant.parameters["second_parameter"] = "second_parameter_value";
443 QtHarmonyExtras::AppContext::restartApp(requestWant);
444 \endcode
445*/
446Q_NORETURN void QOhosAppContextImpl::restartApp(const std::optional<Want> &want)
447{
448 restartAppImpl(want ? std::optional(convertWantToJsonObject(*want)) : std::nullopt);
449}
450
451QOhosAppContextImpl::QOhosAppContextImpl()
452{
453 qRegisterMetaType<std::shared_ptr<QObject>>();
454
455 m_fontSizeScaleSupplier = makeQOhosDataSource<double>(
456 [](QOhosJsState &jsState) -> double {
457 auto optQAbility = jsState.defaultQAbility();
458 if (!optQAbility.has_value())
459 return 1.0;
460 return optQAbility->eval<QNapi::Number>("context.config.fontSizeScale");
461 },
462 [](QOhosJsState &jsState, QOhosConsumer<double> valueUpdatesConsumer) {
463 return registerOhosAppContextEnvironmentCallback(
464 jsState,
465 {
466 {
467 "onConfigurationUpdated",
468 [valueUpdatesConsumer = std::move(valueUpdatesConsumer)](const QOhosCallbackInfo &cbInfo) {
469 auto config = cbInfo.getFirstArg<QNapi::Object>(Q_FUNC_INFO);
470 valueUpdatesConsumer(config.get<QNapi::Number>("fontSizeScale"));
471 }
472 },
473 });
474 },
475 [this](double fontSizeScale) {
476 Q_EMIT fontSizeScaleChanged(fontSizeScale);
477 },
478 QtOhos::invokeInQtThread,
479 Q_FUNC_INFO);
480}
481
482double QOhosAppContextImpl::fontSizeScale() const
483{
484 return m_fontSizeScaleSupplier();
485}
486
487/*!
488 \fn bool QtHarmonyExtras::AppContext::hasSerialPortAccessRight(const QString &portName) const
489
490 Checks whether the application currently has permission to access the serial port identified by \a portName.
491
492 Returns \c true if access rights for the specified serial port are currently granted, otherwise returns \c false.
493
494 This function performs a synchronous check of the current permission state and does not trigger
495 any permission request. To request access rights when they are not yet granted, use
496 requestSerialPortAccessRightIfNeeded().
497
498 For details about the underlying platform API, see
499 \l {https://developer.huawei.com/consumer/en/doc/harmonyos-references/js-apis-serialmanager}
500 {Serial Port Manager}.
501
502 \sa requestSerialPortAccessRightIfNeeded()
503*/
504bool QOhosAppContextImpl::hasSerialPortAccessRight(const QString &portName) const
505{
506 const auto optSerialPortId = tryConvertPortNameToSystemPortId(portName);
507 if (!optSerialPortId.has_value()) {
508 qOhosPrintfError(
509 "%s: cannot convert serial port name '%s' to port id.",
510 Q_FUNC_INFO, portName.toStdString().c_str());
511 return false;
512 }
513
514 return QOhosJsThreadGateway::eval(
515 [&](QOhosJsState &jsState) {
516 return hasSerialPortAccessRightJsImpl(jsState, optSerialPortId.value());
517 },
518 Q_FUNC_INFO);
519}
520
521/*!
522 \fn void QtHarmonyExtras::AppContext::requestSerialPortAccessRightIfNeeded(const QString &portName, QObject *context, std::function<void(std::shared_ptr<QObject>)> callback)
523
524 Requests permission for the application to access the serial port identified by \a portName.
525
526 This function performs an asynchronous permission request. The result of the request is delivered by
527 invoking \a callback on the thread of \a context; if \a context is destroyed before the response
528 arrives, \a callback is not invoked.
529
530 The outcome of the request (granted or denied) is reported asynchronously through \a callback.
531 If access is granted, \a callback provides a context object that must be kept alive for as long as the application
532 requires access to the serial port.
533
534 If access is not granted, \a callback delivers nullptr. This may happen if:
535 \list
536 \li a provided \a portName cannot be mapped to a valid system serial port,
537 \li an error occurred while requesting the access right,
538 \li user denied the access right.
539 \endlist
540
541 For details about the underlying platform API, see
542 \l {https://developer.huawei.com/consumer/en/doc/harmonyos-references/js-apis-serialmanager}
543 {Serial Port Manager}.
544*/
545void QOhosAppContextImpl::requestSerialPortAccessRightIfNeeded(
546 const QString &portName, QObject *context,
547 std::function<void(std::shared_ptr<QObject>)> callback)
548{
549 requestSerialPortAccessRight(
550 portName, context,
551 [callback = std::move(callback)](std::shared_ptr<void> serialPortAccessRightContext) {
552 callback(makeQObjectLifetimeHandleOrNull(serialPortAccessRightContext));
553 });
554}
555
556/*!
557 \fn std::shared_ptr<BundleInfo> QtHarmonyExtras::AppContext::bundleInfo() const
558
559 Returns BundleInfo object for the current application. The obtained information does not
560 contain information about the signature, HAP module, ability, ExtensionAbility, or permission.
561*/
562std::shared_ptr<BundleInfo> QOhosAppContextImpl::bundleInfo() const
563{
564 return createBundleInfo(getCurrentApplicationVersionCode());
565}
566
567}
568
569QT_END_NAMESPACE