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