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
qohosjsmain.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
4#include "qohosjsmain.h"
5
6#include <qplugin.h>
7#include <dlfcn.h>
8#include <node_api.h>
9#include <napi/native_api.h>
10#include <hilog/log.h>
11#include <pthread.h>
12#include <qos/qos.h>
13#include <QtCore/qdebug.h>
14#include <QtCore/qglobal.h>
15#include <QtCore/qjsondocument.h>
16#include <QtCore/qjsonobject.h>
17#include <QtCore/qmap.h>
18#include <QtCore/qwaitcondition.h>
19#include <QtCore/qdir.h>
20#include <QtCore/private/qcore_unix_p.h>
21#include <qohosjsenv_p.h>
22#include <QtCore/qvariant.h>
23#include <QtCore/private/qnapi_p.h>
24#include <QtCore/private/qohosappcontext_p.h>
25#include <QtCore/private/qohoscommon_p.h>
26#include <QtCore/private/qohospermissionshelper_p.h>
27#include <QtCore/private/qcoreapplication_p.h>
30#include <algorithm>
31#include <cerrno>
32#include <chrono>
33#include <cstdio>
34#include <cstring>
35#include <limits>
36#include <qohossinglethreadexecutor.h>
37#include <qpa/qwindowsysteminterface.h>
38#include <map>
39#include <qohosapppermissions_p.h>
40#include <qohosdeviceinfo_p.h>
41#include <qohosenums.h>
42#include <qohospermissionshelperimpl.h>
43#include <qohosplugincore.h>
44#include <signal.h>
45#include <string>
46#include <sys/resource.h>
47#include <type_traits>
48#include <cstdlib>
49#include <unordered_map>
50#include <utility>
51
52#include "private/qohosplatformtheme_p.h"
53#include "qarkui/qxcomponentregistry.h"
56#include "qohosjsutils.h"
61#include "qohosutils.h"
62#include "qohoswatchdog.h"
65#include "render/qxcomponent.h"
66
68
69using namespace std::chrono_literals;
70
72
74struct {
77} s_hotStartIteration;
79extern "C" typedef int (*Main)(int, char **); //use the standard main method to start the application
80
82
84static std::string s_appSharedLibName;
86static std::vector<QNapi::Reference<QNapi::Object>> foregroundAbilities;
87static int s_appExitCode = 0;
88static std::string s_exitCodeFilePath;
89
90static bool s_hotStartEnabled = false;
91
92namespace QtOhos {
93
94namespace {
95
96constexpr const char *enableHotStartEnvVariableName = "IO__QT__OHOS__ENABLE_HOT_START";
97
98constexpr const char *qtMainThreadStackSizeEnvVariableName = "IO__QT__OHOS__QT_MAIN_THREAD_STACK_SIZE";
99
100constexpr std::size_t defaultQtThreadStackSize = 8 * 1024 * 1024;
101
102constexpr auto minSupportedOhosSdkApiVersion = 23;
104
105std::atomic<bool> experimentalEnableGlBackinStore{false};
106std::atomic<bool> debugUseBasicStyleAndTheme{false};
107std::atomic<bool> debugDrawQtRasterBackingStoreFlushedRegion{false};
108std::atomic<bool> vsyncOnSoftwareBackingStoreEnabled{true};
109std::atomic<bool> enableNativeNodeApiKeyEvents{true};
110std::atomic<bool> enableNativeNodeApiMouseEvents{true};
111QtRunMode currentQtRunMode = QtRunMode::Normal;
112
113const auto callerPidWantArgName = "ohos.aafwk.param.callerPid";
114const auto qtAppProcessIdWantArgName = "io.qt.private.appProcessId";
115
116const char *mapQtRunModeToString(QtRunMode qtRunMode)
117{
118 switch (qtRunMode) {
120 return "Normal";
122 return "NoUiChildProcess";
123 }
124
125 qOhosReportFatalErrorAndAbort(
126 "%s: got unknown QtRunMode value: %d",
127 Q_FUNC_INFO, static_cast<int>(qtRunMode));
128}
129
130struct QtAppStartConfig
131{
132 std::string appLibraryPath;
133 bool watchdogEnabled = true;
134};
135
136class AppContextDirs
137{
138public:
139 static AppContextDirs mapFromNapiObject(QNapi::Object appContextObj);
140 static AppContextDirs mapFromQOhosAppContextProperties(const QMap<QOhosAppContext::Type, QString> &props);
141
142 QMap<QOhosAppContext::Type, QString> mapToQOhosAppContextProperties() const;
143 QJsonObject mapToQJsonObject() const;
144
145 std::string bundleCodeDir;
146 std::string cacheDir;
147 std::string filesDir;
148 std::string preferencesDir;
149 std::string tempDir;
150 std::string databaseDir;
151 std::string distributedFilesDir;
152 std::string resourceDir;
153
154private:
155 static const std::pair<const char *, std::string AppContextDirs::*> propsNames[];
156 static const std::pair<QOhosAppContext::Type, std::string AppContextDirs::*> qOhosAppContextPropsMap[];
157};
158
159const std::pair<const char *, std::string AppContextDirs::*> AppContextDirs::propsNames[] = {
160 {"bundleCodeDir", &AppContextDirs::bundleCodeDir},
161 {"cacheDir", &AppContextDirs::cacheDir},
162 {"filesDir", &AppContextDirs::filesDir},
163 {"preferencesDir", &AppContextDirs::preferencesDir},
164 {"tempDir", &AppContextDirs::tempDir},
165 {"databaseDir", &AppContextDirs::databaseDir},
166 {"distributedFilesDir", &AppContextDirs::distributedFilesDir},
167 {"resourceDir", &AppContextDirs::resourceDir},
168};
169
170const std::pair<QOhosAppContext::Type, std::string AppContextDirs::*> AppContextDirs::qOhosAppContextPropsMap[] = {
171 {QOhosAppContext::Type::bundleCodeDir, &AppContextDirs::bundleCodeDir},
172 {QOhosAppContext::Type::cacheDir, &AppContextDirs::cacheDir},
173 {QOhosAppContext::Type::filesDir, &AppContextDirs::filesDir},
174 {QOhosAppContext::Type::preferencesDir, &AppContextDirs::preferencesDir},
175 {QOhosAppContext::Type::tempDir, &AppContextDirs::tempDir},
176 {QOhosAppContext::Type::databaseDir, &AppContextDirs::databaseDir},
177 {QOhosAppContext::Type::distributedFilesDir, &AppContextDirs::distributedFilesDir},
178 {QOhosAppContext::Type::resourceDir, &AppContextDirs::resourceDir},
179};
180
181AppContextDirs AppContextDirs::mapFromNapiObject(QNapi::Object appContextObj)
182{
183 AppContextDirs appContextDirs;
184 for (const auto &propEntry : propsNames)
185 appContextDirs.*propEntry.second = appContextObj.get<QNapi::String>(propEntry.first);
186
187 return appContextDirs;
188}
189
190AppContextDirs AppContextDirs::mapFromQOhosAppContextProperties(
191 const QMap<QOhosAppContext::Type, QString> &props)
192{
193 AppContextDirs appContextDirs;
194 for (const auto &propEntry : qOhosAppContextPropsMap)
195 appContextDirs.*propEntry.second = props[propEntry.first].toStdString();
196 return appContextDirs;
197}
198
199QMap<QOhosAppContext::Type, QString> AppContextDirs::mapToQOhosAppContextProperties() const
200{
201 QMap<QOhosAppContext::Type, QString> qOhosAppContextProps;
202 for (const auto &propEntry : qOhosAppContextPropsMap)
203 qOhosAppContextProps[propEntry.first] = QString::fromStdString(this->*propEntry.second);
204 return qOhosAppContextProps;
205}
206
207QJsonObject AppContextDirs::mapToQJsonObject() const
208{
209 QJsonObject json;
210 for (const auto &propEntry : propsNames)
211 json[QString::fromUtf8(propEntry.first)] = QString::fromStdString(this->*propEntry.second);
212 return json;
213}
214
215std::vector<std::string> splitString(const std::string &inputStr, char separator)
216{
217 constexpr auto separatorSize = 1;
218
219 std::vector<std::string> result;
220 std::size_t currentPos = 0;
221 while (currentPos < inputStr.size()) {
222 auto separatorPos = inputStr.find(separator, currentPos);
223 result.push_back(inputStr.substr(currentPos, separatorPos));
224 currentPos = separatorPos != std::string::npos
225 ? separatorPos + separatorSize
226 : inputStr.size();
227 }
228
229 return result;
230}
231
232std::pair<QOhosConsumer<bool>, std::function<void()>> makeConditionFlagMTAccessors(
233 std::string conditionName)
234{
235 struct Context
236 {
237 std::string conditionName;
238 std::mutex conditionMutex;
239 std::condition_variable conditionCv;
240 bool condition = false;
241 };
242
243 auto context = std::make_shared<Context>();
244 context->conditionName = std::move(conditionName);
245
246 return {
247 [context](bool condition) {
248 qOhosPrintfDebug(
249 "%s: setting condition '%s' to %s",
250 Q_FUNC_INFO, context->conditionName.c_str(), mapBoolToTrueFalseStr(condition));
251 {
252 std::lock_guard<std::mutex> conditionLock(context->conditionMutex);
253 if (condition != context->condition) {
254 context->condition = condition;
255 context->conditionCv.notify_all();
256 }
257 }
258 },
259 [context]() {
260 qOhosPrintfDebug("%s: waiting for condition '%s'", Q_FUNC_INFO, context->conditionName.c_str());
261 {
262 std::unique_lock<std::mutex> conditionLock(context->conditionMutex);
263 context->conditionCv.wait(
264 conditionLock,
265 [&]() {
266 return context->condition;
267 });
268 }
269 qOhosPrintfDebug("%s: condition '%s' met", Q_FUNC_INFO, context->conditionName.c_str());
270 },
271 };
272}
273
274template<typename FuncResult, typename ...FuncArgs, typename FuncFactory>
275auto makeLazyInitFunc(FuncFactory funcFactory) -> std::function<FuncResult(FuncArgs...)>
276{
277 using Func = std::function<FuncResult(FuncArgs...)>;
278 return [func = Func(), factory = QOhosSupplier<Func>(std::move(funcFactory))](FuncArgs ...args) mutable {
279 if (!func)
280 func = std::exchange(factory, nullptr)();
281 return func(std::forward<FuncArgs>(args)...);
282 };
283}
284
285std::function<int(std::vector<std::string>)> openLibraryWithMainFunctionOrFail(const std::string &libraryPath)
286{
287 void *mainLibraryHnd = dlopen(libraryPath.c_str(), RTLD_LAZY);
288 if (Q_UNLIKELY(!mainLibraryHnd)) {
289 qOhosReportFatalErrorAndAbort(
290 "%s: dlopen() failed to open library '%s': %s",
291 Q_FUNC_INFO, libraryPath.c_str(), dlerror());
292 }
293
294 auto *mainFunc = reinterpret_cast<Main>(dlsym(mainLibraryHnd, "main"));
295 if (Q_UNLIKELY(!mainFunc)) {
296 qOhosReportFatalErrorAndAbort(
297 "%s: dlsym() failed to find 'main' symbol in library '%s': %s",
298 Q_FUNC_INFO, libraryPath.c_str(), dlerror());
299 }
300
301 qOhosPrintfDebug("%s: opened library '%s' with main function", Q_FUNC_INFO, libraryPath.c_str());
302
303 return [libraryPath, mainFunc](std::vector<std::string> mainArgs) {
304 auto mainArgsPointers = std::vector<char *>();
305 for (auto &arg : mainArgs)
306 mainArgsPointers.push_back(&arg[0]);
307 mainArgsPointers.push_back(nullptr);
308
309 int argc = mainArgs.size();
310 char **argv = &mainArgsPointers[0];
311
312 qOhosPrintfDebug(
313 "%s: calling 'main' function in library '%s' (argc=%d)",
314 Q_FUNC_INFO, libraryPath.c_str(), argc);
315
316 for (int i = 0; i < argc; ++i)
317 qOhosPrintfDebug("%s: 'main' function argv[%d]='%s'", Q_FUNC_INFO, i, argv[i]);
318
319 int mainResult = mainFunc(argc, argv);
320
321 qOhosPrintfDebug(
322 "%s: 'main' function in library '%s' returned %d",
323 Q_FUNC_INFO, libraryPath.c_str(), mainResult);
324
325 return mainResult;
326 };
327}
328
329class QUiAbilityEngine : public QAbilityEngine
330{
331public:
332 QUiAbilityEngine();
333 ~QUiAbilityEngine();
334
335 QAbilityInfo readAbilityInfo(const QNapi::Object &ability) const override;
336};
337
338QUiAbilityEngine::QUiAbilityEngine() = default;
339
340QUiAbilityEngine::~QUiAbilityEngine() = default;
341
342QAbilityInfo QUiAbilityEngine::readAbilityInfo(const QNapi::Object &ability) const
343{
344 auto abilityInfo = ability.eval<QNapi::Object>("context.abilityInfo");
345
346 return {
347 .name = abilityInfo.get<QNapi::String>("name"),
348 .bundleName = abilityInfo.get<QNapi::String>("bundleName"),
349 .moduleName = abilityInfo.get<QNapi::String>("moduleName"),
350 };
351}
352
353void redirectStandardDescriptorsToFile(const std::string &redirectedStdoutPath)
354{
355 int openResult = qt_safe_open(redirectedStdoutPath.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0666);
356 auto openErrno = errno;
357 if (openResult < 0) {
358 // Non-fatal: the test still needs to write its exit code.
359 qOhosPrintfWarning("%s: error opening file '%s' for redirected stdout: %s",
360 Q_FUNC_INFO, redirectedStdoutPath.c_str(), std::strerror(openErrno));
361 return;
362 }
363
364 ::fflush(stdout);
365
366 if (qt_safe_dup2(openResult, STDOUT_FILENO) < 0) {
367 auto dup2Errno = errno;
368 qOhosPrintfWarning("%s: dup2() failed on redirecting stdout to '%s': %s",
369 Q_FUNC_INFO, redirectedStdoutPath.c_str(), std::strerror(dup2Errno));
370 }
371
372 qt_safe_close(openResult);
373}
374
375QOhosConsumer<std::vector<std::string>> makeAppMainFuncLauncher(
376 const QtAppStartConfig &appStartConfig, QOhosConsumer<int> funcExitHandler)
377{
378 auto appLibraryMainFunc = makeLazyInitFunc<int, std::vector<std::string>>(
379 [appLibraryPath = appStartConfig.appLibraryPath]() {
380 return openLibraryWithMainFunctionOrFail(appLibraryPath);
381 });
382
383 return [appLibraryMainFunc = std::move(appLibraryMainFunc), funcExitHandler = std::move(funcExitHandler), appStartConfig](std::vector<std::string> appArgs) {
384 auto __dbg = make_QCScopedDebugJS("startApplicationMainFunction");
385 std::vector<std::string> mainArgs;
386 mainArgs.push_back(appStartConfig.appLibraryPath);
387 mainArgs.insert(mainArgs.end(), appArgs.begin(), appArgs.end());
388
389 auto qtWatchdog =
390 appStartConfig.watchdogEnabled
392 : std::shared_ptr<void>();
393
394 int exitCode = appLibraryMainFunc(std::move(mainArgs));
395
396 funcExitHandler(exitCode);
397 };
398}
399
400std::vector<std::string> mapJsonArrayToStrings(const std::string &inputJson)
401{
402 using namespace std::string_literals;
403
404 auto doc = QJsonDocument::fromJson(QByteArray::fromStdString(inputJson));
405 if (doc.isNull())
406 throw std::runtime_error("input is not valid JSON string");
407 if (!doc.isArray())
408 throw std::runtime_error("input JSON does not contain an array");
409
410 const auto inputArray = doc.array();
411
412 std::vector<std::string> result;
413 for (const auto &elem : inputArray) {
414 if (!elem.isString())
415 throw std::runtime_error("input array's element is not a string");
416 result.push_back(elem.toString().toStdString());
417 }
418
419 return result;
420}
421
422template<typename T>
423std::enable_if_t<std::is_base_of<QNapi::Value, T>::value, T>
424getWantParamOrEmptyIfNotPresent(QNapi::Object want, const std::string &paramName)
425{
426 return QNapi::getOptionalPropOrEmpty<T>(
427 QNapi::getOptionalPropOrEmpty<QNapi::Object>(want, "parameters"),
428 paramName, "parameters of Want");
429}
430
431std::vector<std::string> getQtAppArgsFromWant(QNapi::Object want)
432{
433 using namespace std::string_literals;
434
435 const auto *qtUseUriAsArgPropName = "io.qt.useUriAsArg";
436 const auto *qtAppArgsPropName = "io.qt.appArgs";
437 const auto *qtAppArgsJsonPropName = "io.qt.appArgsJson";
438
439 Napi::HandleScope getArgsScope(want.Env());
440
441 std::vector<std::string> result;
442
443 auto optQtUseUriAsArg = getWantParamOrEmptyIfNotPresent<QNapi::Boolean>(want, qtUseUriAsArgPropName);
444 if (optQtUseUriAsArg.IsEmpty() || optQtUseUriAsArg.Value()) {
445 auto uri = QNapi::getPropOrUndefined(want, "uri");
446 result.push_back(
447 uri.IsString()
448 ? QNapi::checkedCast<QNapi::String>(uri)
449 : std::string());
450 }
451
452 auto optQtAppArgs = getWantParamOrEmptyIfNotPresent<QNapi::Array>(want, qtAppArgsPropName);
453 auto optQtAppArgsJson = getWantParamOrEmptyIfNotPresent<QNapi::String>(want, qtAppArgsJsonPropName);
454
455 if (!optQtAppArgs.IsEmpty()) {
456 if (!QNapi::arrayElementTypesMatch<QNapi::String>(optQtAppArgs)) {
457 throw QNapi::makeLoggedException(
458 want.Env(), "Want parameter '"s + qtAppArgsPropName + "' is not an array of strings"s);
459 }
460
461 auto qtAppArgsStrings = QNapi::getArrayElements<std::vector<std::string>, QNapi::String>(optQtAppArgs);
462 result.insert(result.end(), qtAppArgsStrings.begin(), qtAppArgsStrings.end());
463 } else if (!optQtAppArgsJson.IsEmpty()) {
464 std::vector<std::string> qtAppArgsJsonStrings;
465 try {
466 qtAppArgsJsonStrings = mapJsonArrayToStrings(optQtAppArgsJson);
467 } catch (const std::exception &e) {
468 throw QNapi::makeLoggedException(
469 want.Env(), "Want parameter '"s + qtAppArgsJsonPropName + "' is invalid: "s + e.what());
470 }
471 result.insert(result.end(), qtAppArgsJsonStrings.begin(), qtAppArgsJsonStrings.end());
472 }
473
474 return result;
475}
476
477void requestAppPermissionsInBackground(JsState &jsState, const std::vector<std::string> &permissionsNames)
478{
479 for (const auto &permissionName : permissionsNames) {
480 qOhosPrintfInfo(
481 "Qt: automatically requesting application permission: '%s'",
482 permissionName.c_str());
483
484 QOhosAppPermissions::requestAppPermissionFromUser(
485 jsState, permissionName,
486 [permissionName](JsState &, bool permissionGranted) {
487 if (permissionGranted) {
488 qOhosPrintfInfo(
489 "Qt: automatically requested application permission granted: '%s'",
490 permissionName.c_str());
491 } else {
492 qOhosPrintfWarning(
493 "Qt: automatically requested application permission rejected: '%s'",
494 permissionName.c_str());
495 }
496 });
497 }
498}
499
500struct AppProcessLaunchOptions
501{
502 QNapi::Boolean useDefaultUiAbilityInstanceInQt;
503 QNapi::String appSharedLibNameOverride;
504 QNapi::Boolean experimentalGlBackingStore;
505 QNapi::Boolean debugDrawQtRasterBackingStoreFlushedRegion;
506 QNapi::Boolean debugUseBasicStyleAndTheme;
507 QNapi::Boolean enableVsyncOnSoftwareBackingStore;
508 QNapi::Boolean watchdogEnabled;
509 QNapi::String redirectStdoutToFile;
510 QNapi::String exitCodeFile;
511 QNapi::String autoRequestPermissions;
512 QNapi::Boolean enableNativeNodeApiKeyEvents;
513 QNapi::Boolean enableNativeNodeApiMouseEvents;
514};
515
516AppProcessLaunchOptions getProcessLaunchOptionsFromWant(QNapi::Object launchWant)
517{
518 auto assignWantParamIfPresent = [&](auto &outputValue, const char *paramName) {
519 using Param = std::remove_reference_t<decltype(outputValue)>;
520 outputValue = getWantParamOrEmptyIfNotPresent<Param>(launchWant, paramName);
521 };
522
523 AppProcessLaunchOptions launchOpts;
524
525 assignWantParamIfPresent(
526 launchOpts.useDefaultUiAbilityInstanceInQt,
527 "io.qt.useDefaultUiAbilityInstanceInQt");
528 assignWantParamIfPresent(
529 launchOpts.appSharedLibNameOverride,
530 "io.qt.appSharedLibNameOverride");
531 assignWantParamIfPresent(
532 launchOpts.experimentalGlBackingStore,
533 "io.qt.experimental.enableGlBackingStore");
534 assignWantParamIfPresent(
535 launchOpts.debugDrawQtRasterBackingStoreFlushedRegion,
536 "io.qt.debug.drawQtRasterBackingStoreFlushedRegion");
537 assignWantParamIfPresent(
538 launchOpts.debugUseBasicStyleAndTheme,
539 "io.qt.debug.useBasicStyleAndTheme");
540 assignWantParamIfPresent(
541 launchOpts.enableVsyncOnSoftwareBackingStore,
542 "io.qt.experimental.enableVsyncOnSoftwareBackingStore");
543 assignWantParamIfPresent(
544 launchOpts.watchdogEnabled,
545 "io.qt.watchdogEnabled");
546 assignWantParamIfPresent(
547 launchOpts.redirectStdoutToFile,
548 "io.qt.debug.redirectedStdoutPath");
549 assignWantParamIfPresent(
550 launchOpts.exitCodeFile,
551 "io.qt.debug.exitCodePath");
552 assignWantParamIfPresent(
553 launchOpts.autoRequestPermissions,
554 "io.qt.debug.autoRequestPermissions");
555 assignWantParamIfPresent(
556 launchOpts.enableNativeNodeApiKeyEvents,
557 "io.qt.experimental.enableNativeNodeApiKeyEvents");
558 assignWantParamIfPresent(
559 launchOpts.enableNativeNodeApiMouseEvents,
560 "io.qt.experimental.enableNativeNodeApiMouseEvents");
561
562 return launchOpts;
563}
564
565void setGlobalFlagsFromAppProcessLaunchOptions(const AppProcessLaunchOptions &launchOpts)
566{
567 if (!launchOpts.useDefaultUiAbilityInstanceInQt.IsEmpty())
568 s_autoStartedAbilityInstanceWaitingForQtWindow = launchOpts.useDefaultUiAbilityInstanceInQt.Value();
569
570 if (!launchOpts.experimentalGlBackingStore.IsEmpty())
571 experimentalEnableGlBackinStore = launchOpts.experimentalGlBackingStore.Value();
572
573 if (!launchOpts.debugUseBasicStyleAndTheme.IsEmpty())
574 debugUseBasicStyleAndTheme = launchOpts.debugUseBasicStyleAndTheme.Value();
575
576 if (!launchOpts.enableVsyncOnSoftwareBackingStore.IsEmpty())
577 vsyncOnSoftwareBackingStoreEnabled = launchOpts.enableVsyncOnSoftwareBackingStore.Value();
578
579 debugDrawQtRasterBackingStoreFlushedRegion =
580 launchOpts.debugDrawQtRasterBackingStoreFlushedRegion.IsEmpty()
581 ? false
582 : launchOpts.debugDrawQtRasterBackingStoreFlushedRegion.Value();
583
584 if (!launchOpts.enableNativeNodeApiKeyEvents.IsEmpty())
585 enableNativeNodeApiKeyEvents = launchOpts.enableNativeNodeApiKeyEvents.Value();
586
587 if (!launchOpts.enableNativeNodeApiMouseEvents.IsEmpty())
588 enableNativeNodeApiMouseEvents = launchOpts.enableNativeNodeApiMouseEvents.Value();
589}
590
591void terminateAllAbilityInstances(JsState &jsState, const char *logContext)
592{
594 [&](auto qAbilityPeer) {
595 qOhosPrintfInfo(
596 "Qt: terminating QAbility with instanceId='%s'",
597 qAbilityPeer->instanceId().c_str());
598 auto optQUiAbilityPeer = QUiAbilityPeer::tryCastFromQAbilityPeerOrNull(qAbilityPeer);
599 if (optQUiAbilityPeer)
600 JsWindowsTracker::tagWindowAsClosing(optQUiAbilityPeer->window(), logContext);
601 qAbilityPeer->qAbility().call("context.terminateSelf");
602 });
603}
604
605QOhosOptional<std::size_t> tryGetMaxStackSizeHardLimit()
606{
607 struct ::rlimit limit;
608 if (::getrlimit(RLIMIT_STACK, &limit) != 0) {
609 auto getrlimitErrno = errno;
610 qOhosPrintfWarning(
611 "%s: error reading stack size hard limit (assuming no limit): %s",
612 Q_FUNC_INFO, std::strerror(getrlimitErrno));
613 return {};
614 }
615
616 return limit.rlim_max != RLIM_INFINITY
617 ? QOhosOptional<std::size_t>(limit.rlim_max)
618 : makeEmptyQOhosOptional();
619}
620
621QOhosOptional<std::size_t> tryGetQtThreadStackSizeFromEnv()
622{
623 int stackSizeFromEnv = qEnvironmentVariableIntValue(qtMainThreadStackSizeEnvVariableName);
624 return stackSizeFromEnv > 0
625 ? makeQOhosOptional(static_cast<std::size_t>(stackSizeFromEnv))
627}
628
629std::size_t getPreferredStackSizeForQtThread()
630{
631 constexpr std::size_t qtRequiredMinStackSize = 40960;
632 std::size_t pthreadStackMin = PTHREAD_STACK_MIN;
633 auto minStackSize = std::max({qtRequiredMinStackSize, pthreadStackMin});
634 auto maxStackSize = tryGetMaxStackSizeHardLimit().value_or(std::numeric_limits<std::size_t>::max());
635
636 auto optRequestedStackSize = tryGetQtThreadStackSizeFromEnv();
637
638 if (optRequestedStackSize.has_value()) {
639 auto requestedStackSize = optRequestedStackSize.value();
640 if (requestedStackSize < minStackSize) {
641 qOhosPrintfWarning(
642 "%s: requested stack size (%zu) is below minimum (pthread min: %zu, Qt min: %zu), increasing",
643 Q_FUNC_INFO, requestedStackSize, pthreadStackMin, qtRequiredMinStackSize);
644 }
645 if (requestedStackSize > maxStackSize) {
646 qOhosPrintfWarning(
647 "%s: requested stack size (%zu) is above maximum (hard limit: %zu), decreasing",
648 Q_FUNC_INFO, requestedStackSize, maxStackSize);
649 }
650 }
651
652 auto preferredStackSize = qBound(
653 minStackSize, optRequestedStackSize.value_or(defaultQtThreadStackSize), maxStackSize);
654
655 qOhosPrintfInfo("%s: preferred stack size for Qt Thread: %zu", Q_FUNC_INFO, preferredStackSize);
656
657 return preferredStackSize;
658}
659
660QOhosConsumer<std::vector<std::string>> makeQtThreadWithMainFuncLauncher(
661 QOhosConsumer<std::vector<std::string>> baseMainFuncLauncher)
662{
663 SingleThreadExecutorConfig qtThreadExecutorConfig = {
664 .threadPreferredStackSize = makeQOhosOptional(getPreferredStackSizeForQtThread()),
665 };
666 auto qtThreadExecutor = makeSingleThreadExecutor(qtThreadExecutorConfig);
667
668 struct InitContext
669 {
670 std::mutex initializedMutex;
671 std::condition_variable initializedCv;
672 bool initialized = false;
673 };
674
675 auto initContext = std::make_shared<InitContext>();
676
677 qtThreadExecutor(
678 [initContext] {
679 pthread_setname_np(pthread_self(), "QtMainThread");
680 //
681 // Following call to QThread::currentThread() forces this thread to be
682 // the main Qt/GUI thread. It sets the QCoreApplication::theMainThread field
683 // if this call is the first one.
684 //
685 auto *currentThread = QThread::currentThread();
686 QThread *mainThread = QCoreApplicationPrivate::theMainThread;
687 if (mainThread != currentThread) {
688 qOhosReportFatalErrorAndAbort(
689 "%s: mainThread (%p) != currentThread (%p). Qt API was likely used before Qt initialization. Aborting.",
690 Q_FUNC_INFO, mainThread, currentThread);
691 }
692
693 qt_setQOhosPermissionsHelper(getQOhosPermissionsHelperImpl());
694
695 QtOhos::initQtThreadState();
696
697 {
698 std::lock_guard<std::mutex> initializedLock(initContext->initializedMutex);
699 initContext->initialized = true;
700 initContext->initializedCv.notify_one();
701 }
702 });
703
704 {
705 std::unique_lock<std::mutex> initializedLock(initContext->initializedMutex);
706 initContext->initializedCv.wait(
707 initializedLock,
708 [&]() {
709 return initContext->initialized;
710 });
711 }
712
713 auto sharedBaseMainFuncLauncher = moveToSharedPtr(std::move(baseMainFuncLauncher));
714
715 return [qtThreadExecutor = std::move(qtThreadExecutor), sharedBaseMainFuncLauncher](std::vector<std::string> appArgs) {
716 qtThreadExecutor(
717 [sharedBaseMainFuncLauncher, appArgs = std::move(appArgs)]() mutable {
718 (*sharedBaseMainFuncLauncher)(std::move(appArgs));
719 });
720 };
721}
722
723std::shared_ptr<QAbilityInstancesManager> &getQAbilityInstancesManagerPtr()
724{
725 static std::shared_ptr<QAbilityInstancesManager> instancePtr;
726 return instancePtr;
727}
728
729QAbilityInstancesManager &getQAbilityInstancesManager()
730{
731 return *getQAbilityInstancesManagerPtr();
732}
733
734void handleDefaultQAbilityInstanceStartup(JsState &jsState, std::shared_ptr<QAbilityPeer> qAbilityPeer)
735{
736 QNapi::Object launchWant = qAbilityPeer->launchWant();
737
738 if (!s_qtAppThreadMainFuncLauncher) {
739 auto qtAppThreadIdleSetFunc = std::make_shared<QOhosConsumer<bool>>();
740 std::function<void()> qtAppThreadIdleStateWaitFunc;
741 std::tie(*qtAppThreadIdleSetFunc, qtAppThreadIdleStateWaitFunc) = makeConditionFlagMTAccessors("Qt thread idle");
742
743 auto launchOpts = getProcessLaunchOptionsFromWant(launchWant);
744
745 setGlobalFlagsFromAppProcessLaunchOptions(launchOpts);
746
747 if (!launchOpts.redirectStdoutToFile.IsEmpty())
748 redirectStandardDescriptorsToFile(launchOpts.redirectStdoutToFile.Utf8Value());
749
750 if (!launchOpts.exitCodeFile.IsEmpty())
751 s_exitCodeFilePath = launchOpts.exitCodeFile.Utf8Value();
752
753 if (!launchOpts.autoRequestPermissions.IsEmpty())
754 requestAppPermissionsInBackground(jsState, splitString(launchOpts.autoRequestPermissions, ','));
755
756 std::string appSharedLibName =
757 !launchOpts.appSharedLibNameOverride.IsEmpty()
758 ? launchOpts.appSharedLibNameOverride
760
761 auto appMainFuncLauncher = makeAppMainFuncLauncher(
762 {
763 .appLibraryPath = s_appSharedLibsDirPath + "/" + appSharedLibName,
764 .watchdogEnabled = !launchOpts.watchdogEnabled.IsEmpty()
765 ? launchOpts.watchdogEnabled.Value()
766 : true,
767 },
768 [qtAppThreadIdleSetFunc](int exitCode) {
769 if (s_hotStartEnabled)
770 s_autoStartedAbilityInstanceWaitingForQtWindow = true;
771 else
772 s_appExitCode = exitCode;
773
774 qOhosPrintfInfo("Qt: asynchronously terminating remaining QAbility instances, if any");
775 QtOhos::invokeInJsThread(
776 [](QtOhos::JsState &jsState) {
777 if (s_hotStartEnabled)
778 getQAbilityInstancesManager().registerPendingAutoStartedInstance();
779
780 qOhosPrintfInfo(
781 "Qt: force-resolving pending QWindow destroy Promises before termination if needed");
782 jsState.visitEachQAbilityPeer(
783 [&](std::shared_ptr<QtOhos::QAbilityPeer> peer) {
784 peer->forceResolveQWindowDestroyPromiseIfPresent(
785 Napi::Env(jsState.env()));
786 });
787
788 terminateAllAbilityInstances(jsState, "Qt main() exit");
789 });
790
791 (*qtAppThreadIdleSetFunc)(true);
792 });
793
794 auto mainFuncLauncher = moveToSharedPtr(
795 makeQtThreadWithMainFuncLauncher(std::move(appMainFuncLauncher)));
796 s_qtAppThreadMainFuncLauncher = [mainFuncLauncher, qtAppThreadIdleSetFunc](std::vector<std::string> appArgs) {
797 (*qtAppThreadIdleSetFunc)(false);
798 s_hotStartIteration.activeInQtThread = s_hotStartIteration.activeInQtThread.value_or(0) + 1;
799 (*mainFuncLauncher)(std::move(appArgs));
800 };
801 s_qtAppThreadIdleStateWaitFunc = std::move(qtAppThreadIdleStateWaitFunc);
802 }
803
804 s_hotStartIteration.lastRequestedInJsThread = s_hotStartIteration.lastRequestedInJsThread.value_or(0) + 1;
806 s_appArgs
807 ? *s_appArgs
808 : getQtAppArgsFromWant(launchWant));
809}
810
811std::map<std::string, QNapi::Reference<QNapi::Function>> makeJsModulesFactoriesMap(
812 const QNapi::Object &jsModulesFactoriesObj)
813{
814 std::map<std::string, QNapi::Reference<QNapi::Function>> jsModulesFactoriesMap;
815 for (const auto &prop : jsModulesFactoriesObj) {
816 if (prop.first.IsString()) {
817 auto propName = QNapi::checkedCast<QNapi::String>(prop.first);
818 QNapi::Value propValue = prop.second;
819 if (propValue.IsFunction()) {
820 jsModulesFactoriesMap.emplace(
821 propName.Utf8Value(),
822 QNapi::Reference<QNapi::Function>::makePersistentFrom(
823 QNapi::checkedCast<QNapi::Function>(propValue)));
824 }
825 }
826 }
827
828 return jsModulesFactoriesMap;
829}
830
831class AppFunctionsImpl : public AppFunctions
832{
833public:
834 void startQAbilityInstance(
835 QNapi::Object baseQAbility, QObjectThreadSafeRef qwindow,
836 QNapi::Object optStartOptions,
837 std::function<void(JsState &, std::shared_ptr<QAbilityPeer>)> startupNotifyFunc) override;
838
839 void startAppProcess(
840 QNapi::Object baseQAbility, const std::string &processId, QNapi::Object requestWant,
841 QNapi::Object optStartOptions) override;
842
843 void startAppProcess(
844 QNapi::Object baseQAbility, const std::string &processId, QNapi::Object requestWant,
845 QNapi::Object optStartOptions, std::function<void(JsState &)> continueFunc) override;
846
847 void startNoUiChildProcess(JsState &jsState, const std::string &libraryName, const std::vector<std::string> &args) override;
848
849 void tagWidgetOrWindowAsFloatWindow(QObject *widgetOrWindow, bool floatWindowEnabled) override;
850
851private:
852 QNapi::Promise startAppProcessImpl(
853 QNapi::Object baseQAbility, const std::string &processId, QNapi::Object requestWant,
854 QNapi::Object optStartOptions);
855};
856
857void AppFunctionsImpl::startQAbilityInstance(
858 QNapi::Object baseQAbility, QObjectThreadSafeRef qwindow,
859 QNapi::Object optStartOptions,
860 std::function<void(JsState &, std::shared_ptr<QAbilityPeer>)> startupNotifyFunc)
861{
862 getQAbilityInstancesManager().startNewInstance(
863 baseQAbility, qwindow, optStartOptions, std::move(startupNotifyFunc));
864}
865
866void AppFunctionsImpl::startAppProcess(
867 QNapi::Object baseQAbility, const std::string &processId, QNapi::Object requestWant,
868 QNapi::Object optStartOptions)
869{
870 std::ignore = startAppProcessImpl(baseQAbility, processId, requestWant, optStartOptions);
871}
872
873void AppFunctionsImpl::startAppProcess(
874 QNapi::Object baseQAbility, const std::string &processId, QNapi::Object requestWant,
875 QNapi::Object optStartOptions, std::function<void(JsState &)> continueFunc)
876{
877 startAppProcessImpl(baseQAbility, processId, requestWant, optStartOptions)
878 .onCatch(QtOhos::makeErrorLoggingJsCallback("startAbility()"))
879 .onFinally(
880 [continueFunc = std::move(continueFunc)](const CallbackInfo &cbInfo) {
881 continueFunc(cbInfo.jsState());
882 });
883}
884
885QNapi::Promise AppFunctionsImpl::startAppProcessImpl(
886 QNapi::Object baseQAbility, const std::string &processId, QNapi::Object requestWant,
887 QNapi::Object optStartOptions)
888{
889 auto __dbg = make_QCScopedDebugJS("AppFunctionsImpl::startAppProcess");
890
891 static const char * const clonedWantPropsNames[] = {
892 "uri",
893 "type",
894 "action",
895 "flags",
896 "entities",
897 };
898
899 auto env = baseQAbility.Env();
900
901 auto qAbilityInfo = getQAbilityInstancesManager().abilityEngine()->readAbilityInfo(baseQAbility);
902
903 auto startWantParams = QNapi::Object::New(env);
904 auto requestWantParams = QNapi::getOptionalPropOrEmpty<QNapi::Object>(requestWant, "parameters");
905 if (!requestWantParams.IsEmpty()) {
906 for (const auto &requestWantParamEntry : requestWantParams) {
907 startWantParams.Set(
908 requestWantParamEntry.first, static_cast<QNapi::Value>(requestWantParamEntry.second));
909 }
910 }
911 startWantParams.Set(qtAppProcessIdWantArgName, processId);
912
913 auto startWant = QNapi::makeObject(
914 env,
915 {
916 {"bundleName", qAbilityInfo.bundleName},
917 {"moduleName", qAbilityInfo.moduleName},
918 {"abilityName", qAbilityInfo.name},
919 {"parameters", startWantParams},
920 });
921
922 for (const auto &propName : clonedWantPropsNames) {
923 auto optProp = QNapi::getOptionalPropOrEmpty<QNapi::Value>(requestWant, propName);
924 if (!optProp.IsEmpty())
925 startWant.Set(propName, optProp);
926 }
927
928 std::vector<QNapi::ValueWrapper> startAbilityArgs = {startWant};
929 if (!optStartOptions.IsEmpty())
930 startAbilityArgs.push_back(optStartOptions);
931 return baseQAbility.evalToPromiseOrRejectOnThrow("context.startAbility(*)", startAbilityArgs);
932}
933
934void AppFunctionsImpl::startNoUiChildProcess(
935 JsState &jsState, const std::string &libraryName, const std::vector<std::string> &args)
936{
937 const auto *childProcessSrcEntry = "./ets/process/QChildProcess.ets";
938
939 // FIXME:
940 // We want to use childProcessManager.StartMode.APP_SPAWN_FORK here, but the "StartMode"
941 // is defined as "const enum" in the TS code, which makes it a compile-time-only thing
942 // (contrary to non-const enums, which are real objects, const enums don't exist at runtime).
943 // We should consider adding a separate mechanism for handling const enums in the code
944 // if we have more of them in the future.
945 constexpr int startModeAppSpawnFork = 1;
946
947 auto appContextDirs = AppContextDirs::mapFromQOhosAppContextProperties(QOhosAppContext::getAllProperties());
948
949 QJsonArray argsArray;
950 std::transform(args.begin(), args.end(), std::back_inserter(argsArray), QString::fromStdString);
951
952 QJsonObject childSetupJson = {
953 {QString::fromUtf8("appContext"), appContextDirs.mapToQJsonObject()},
954 {QString::fromUtf8("appName"), QString::fromStdString(libraryName)},
955 {QString::fromUtf8("appArgs"), argsArray},
956 };
957
958 jsState.eval(
959 "@ohos.app.ability.childProcessManager.startChildProcess(*)",
960 {
961 childProcessSrcEntry,
962 startModeAppSpawnFork,
963 [childSetupJson](const CallbackInfo &cbInfo) {
964 QNapi::Value error;
965 QNapi::Value data;
966 cbInfo.getLeadingArgs(Q_FUNC_INFO, error, data);
967
968 int childPid = data.IsNumber()
969 ? QNapi::checkedCast<QNapi::Number>(data)
970 : -1;
971
972 if (childPid > 0) {
973 qOhosPrintfDebug("%s: child started: %d", Q_FUNC_INFO, childPid);
974 sendChildProcessSetupData(childPid, childSetupJson);
975 } else {
976 qOhosPrintfError("%s: child NOT started", Q_FUNC_INFO);
977 }
978 },
979 });
980}
981
982void AppFunctionsImpl::tagWidgetOrWindowAsFloatWindow(QObject *widgetOrWindow, bool floatWindowEnabled)
983{
984 QOhosPlatformWindow::tagWindowOrWidgetAsFloatWindow(widgetOrWindow, floatWindowEnabled);
985}
986
987void handleAbilityOnForeground(const CallbackInfo &cbInfo)
988{
989 const auto qAbility = cbInfo.getFirstArg<QNapi::Object>(Q_FUNC_INFO);
990
991 if (foregroundAbilities.empty()) {
992 QtOhos::invokeInQtThread([]() {
993 const auto setQosRes = OH_QoS_SetThreadQoS(QoS_Level::QOS_USER_INTERACTIVE);
994 if (setQosRes != 0) {
995 qOhosWarning(QtForOhos)
996 << "Setting QoS level of Qt thread to USER_INTERACTIVE failed, error code:"
997 << setQosRes;
998 }
999 updateApplicationState(Qt::ApplicationActive);
1000 });
1001 }
1002
1003 foregroundAbilities.push_back(Napi::Persistent(qAbility));
1004}
1005
1006void handleAbilityOnBackground(const CallbackInfo &cbInfo)
1007{
1008 const auto qAbility = cbInfo.getFirstArg<QNapi::Object>(Q_FUNC_INFO);
1009
1010 const auto qAbilityRef = Napi::Persistent(qAbility);
1011 foregroundAbilities.erase(
1012 std::remove(foregroundAbilities.begin(), foregroundAbilities.end(), qAbilityRef),
1013 foregroundAbilities.end());
1014
1015 if (foregroundAbilities.empty()) {
1016 QtOhos::invokeInQtThread([]() {
1017 const auto resetQosRes = OH_QoS_ResetThreadQoS();
1018 if (resetQosRes != 0) {
1019 qOhosWarning(QtForOhos)
1020 << "Resetting QoS level of Qt thread failed, error code:"
1021 << resetQosRes;
1022 }
1023 updateApplicationState(Qt::ApplicationHidden);
1024 updateApplicationState(Qt::ApplicationInactive);
1025 });
1026 }
1027}
1028
1029QNapi::Value handleAbilityOnContinue(const CallbackInfo &cbInfo)
1030{
1031 QNapi::Object qAbility;
1032 QNapi::Object wantParamsObj;
1033 cbInfo.getLeadingArgs(Q_FUNC_INFO, qAbility, wantParamsObj);
1034
1036 cbInfo.jsState().tryGetQAbilityPeerByInstance(qAbility));
1037 if (!uiAbilityPeer) {
1038 qOhosPrintfWarning("%s: got unknown Ability, rejecting", Q_FUNC_INFO);
1039 return makeResolvedPromise(
1040 cbInfo.jsState().mapOhosEnumToJs(
1041 QOhosAbilityOnContinueResult::REJECT));
1042 }
1043
1044 return adaptAsyncCallResultToJsPromise<QOhosAbilityOnContinueResult>(
1045 cbInfo.jsState(),
1046 [](JsState &jsState, auto result) {
1047 return jsState.mapOhosEnumToJs(result);
1048 },
1049 [&](JsState &jsState, auto resultConsumer) {
1050 getQAbilityInstancesManager().getAbilityPeerBackend(uiAbilityPeer)->handleOnContinueRequestFromSystem(
1051 jsState, wantParamsObj, std::move(resultConsumer));
1052 });
1053}
1054
1055std::string targetLibraryDirectory() {
1056 #if defined(Q_PROCESSOR_ARM_64)
1057 return "/libs/arm64";
1058 #elif defined(Q_PROCESSOR_ARM_32)
1059 return "/libs/arm";
1060 #elif defined(Q_PROCESSOR_X86_64)
1061 return "/libs/x86_64";
1062 #else
1063 #error "Unknown system architecture, aborting!"
1064 #endif
1065}
1066
1067void tryDetectBrokenWant(JsState &jsState, QNapi::Object want)
1068{
1069 Napi::HandleScope checkScope(want.Env());
1070
1071 auto defaultQAbility = jsState.defaultQAbilityPeer()->qAbility();
1072
1073 if (!defaultQAbility.IsEmpty()) {
1074 bool fromThisApp = getQAbilityInstancesManager().isWantFromThisApp(defaultQAbility, want);
1075 auto optCallerPid = getWantParamOrEmptyIfNotPresent<QNapi::Number>(want, callerPidWantArgName);
1076 if (fromThisApp && !optCallerPid.IsEmpty() && ::kill(optCallerPid.Int64Value(), 0) != 0) {
1077 qOhosPrintfError(
1078 "%s: got Want from non-existing app process (pid: %lld), which most likely means that we received"
1079 " broken Want (platform bug). That's fatal error for us!",
1080 Q_FUNC_INFO, static_cast<long long>(optCallerPid.Int64Value()));
1081 std::abort();
1082 }
1083 }
1084}
1085
1086std::string readInitialBytesOfFile(const std::string &filePath, std::size_t maxReadSize)
1087{
1088 FILE *inputFile = std::fopen(filePath.c_str(), "rb");
1089 if (inputFile == nullptr) {
1090 auto fopenErrno = errno;
1091 qOhosReportFatalErrorAndAbort(
1092 "%s: can't open file '%s': %s",
1093 Q_FUNC_INFO, filePath.c_str(), std::strerror(fopenErrno));
1094 }
1095
1096 std::unique_ptr<FILE, decltype(&std::fclose)> inputFileCloseGuard(inputFile, &std::fclose);
1097
1098 auto readBuffer = std::string(maxReadSize, '\0');
1099
1100 auto bytesRead = std::fread(&readBuffer[0], 1, maxReadSize, inputFile);
1101 if (std::ferror(inputFile)) {
1102 qOhosReportFatalErrorAndAbort(
1103 "%s: error reading '%s': (%zu bytes read)",
1104 Q_FUNC_INFO, filePath.c_str(), bytesRead);
1105 }
1106
1107 readBuffer.resize(bytesRead);
1108 readBuffer.shrink_to_fit();
1109
1110 return readBuffer;
1111}
1112
1113std::string readCurrentProcessNameFromProcFs()
1114{
1115 const auto *procCmdlinePath = "/proc/self/cmdline";
1116 auto procCmdlineData = readInitialBytesOfFile(procCmdlinePath, 64 * 1024);
1117
1118 auto processNameTerminatorPos = procCmdlineData.find('\0');
1119 if (processNameTerminatorPos == std::string::npos)
1120 qOhosReportFatalErrorAndAbort("%s: found unexpected content in '%s'", Q_FUNC_INFO, procCmdlinePath);
1121
1122 procCmdlineData.resize(processNameTerminatorPos);
1123 procCmdlineData.shrink_to_fit();
1124
1125 return procCmdlineData;
1126}
1127
1128bool isThisEmbeddedUIExtensionProcess(const QNapi::Object &appContext)
1129{
1130 std::string appName = appContext.eval<QNapi::String>("applicationInfo.name");
1131 return readCurrentProcessNameFromProcFs() == appName + ":embeddedUI";
1132}
1133
1134void handleAbilityStageOnCreate(const CallbackInfo &cbInfo)
1135{
1136 auto abilityStage = cbInfo.getFirstArg<QNapi::Object>(Q_FUNC_INFO);
1137
1138 if (qEnvironmentVariableIntValue(enableHotStartEnvVariableName) != 0) {
1139 auto appContext = abilityStage.eval<QNapi::Object>("context.getApplicationContext()");
1140 if (!isThisEmbeddedUIExtensionProcess(appContext)) {
1141 try {
1142 appContext.eval<QNapi::Value>("setSupportedProcessCache(*)", {true});
1143 s_hotStartEnabled = true;
1144 } catch (const Napi::Error &error) {
1145 qOhosPrintfError("setSupportedProcessCache() failed with error: %s", error.what());
1146 }
1147 }
1148 }
1149
1150 qOhosPrintfInfo(
1151 "AbilityStage::onCreate: hot start enabled: %s",
1153}
1154
1155QNapi::Value handleAbilityStageOnNewProcessRequest(const CallbackInfo &cbInfo)
1156{
1157 auto __dbg = make_QCScopedDebugJS(Q_FUNC_INFO);
1158
1159 QNapi::Object qAbilityStage;
1160 QNapi::Object want;
1161 cbInfo.getLeadingArgs(Q_FUNC_INFO, qAbilityStage, want);
1162
1163 qOhosPrintfInfo("AbilityStage::onNewProcessRequest: input Want: %s", QNapi::toJsonString(want).c_str());
1164
1165 tryDetectBrokenWant(cbInfo.jsState(), want);
1166
1167 auto optProcessIdParam = getWantParamOrEmptyIfNotPresent<QNapi::String>(want, qtAppProcessIdWantArgName);
1168
1169 std::string processId = !optProcessIdParam.IsEmpty() ? optProcessIdParam : std::string();
1170
1171 qOhosPrintfInfo("AbilityStage::onNewProcessRequest: returning processId='%s'", processId.c_str());
1172
1173 return QNapi::String::New(cbInfo.Env(), processId);
1174}
1175
1176QNapi::Value handleAbilityStageOnAcceptWant(const CallbackInfo &cbInfo)
1177{
1178 using namespace std::string_literals;
1179
1180 QNapi::Object qAbilityStage;
1181 QNapi::Object want;
1182 cbInfo.getLeadingArgs(Q_FUNC_INFO, qAbilityStage, want);
1183
1184 qOhosPrintfInfo("AbilityStage::onAcceptWant: input Want: %s", QNapi::toJsonString(want).c_str());
1185
1186 tryDetectBrokenWant(cbInfo.jsState(), want);
1187
1188 auto optProcessIdParam = getWantParamOrEmptyIfNotPresent<QNapi::String>(want, qtAppProcessIdWantArgName);
1189 if (!optProcessIdParam.IsEmpty()) {
1190 std::string processIdParamStr = QNapi::checkedCast<QNapi::String>(optProcessIdParam);
1191 auto instanceId = "//"s + processIdParamStr;
1192 qOhosPrintfInfo("AbilityStage::onAcceptWant: returning instanceId='%s'", instanceId.c_str());
1193 return QNapi::String::New(cbInfo.Env(), instanceId);
1194 }
1195
1196 std::shared_ptr<QAbilityPeer> defaultQAbilityPeer = cbInfo.jsState().defaultQAbilityPeer();
1197
1198 auto defaultQAbility = defaultQAbilityPeer->qAbility();
1199 auto receivedQAbilityInstanceId =
1200 !defaultQAbility.IsEmpty()
1201 ? getQAbilityInstancesManager().tryGetQAbilityInstanceIdFromWant(defaultQAbility, want)
1203
1204 auto qAbilityInstanceId =
1205 receivedQAbilityInstanceId.has_value()
1206 ? receivedQAbilityInstanceId.value()
1207 : getQAbilityInstancesManager().pendingAutoStartedInstanceId().value_or(
1208 defaultQAbilityPeer->instanceId());
1209
1210 qOhosPrintfInfo("AbilityStage::onAcceptWant: returning instanceId='%s'", qAbilityInstanceId.c_str());
1211
1212 return QNapi::String::New(cbInfo.Env(), qAbilityInstanceId);
1213}
1214
1215void asyncRunTaskInTemporaryThread(std::function<void()> task, std::function<void(JsState &)> continueFunc)
1216{
1217 auto taskRunnerThread = std::thread(
1218 [task = std::move(task), continueFunc = std::move(continueFunc)]() {
1219 task();
1220 QtOhos::invokeInJsThread(std::move(continueFunc));
1221 });
1222 taskRunnerThread.detach();
1223}
1224
1225void asyncRunTaskInTemporaryThreadWithTimeout(
1226 JsState &jsState, std::function<void()> task, std::chrono::milliseconds waitTimeout,
1227 QOhosConsumer<JsState &, bool> successConsumer)
1228{
1229 auto successNotifyFunc = moveToSharedPtr(
1230 makeCallOnceConsumerWrapper<JsState &, bool>(
1231 std::move(successConsumer)));
1232
1233 setJsTimeout(
1234 jsState,
1235 [successNotifyFunc](const CallbackInfo &cbInfo) {
1236 (*successNotifyFunc)(cbInfo.jsState(), false);
1237 },
1238 waitTimeout);
1239
1240 asyncRunTaskInTemporaryThread(
1241 std::move(task),
1242 [successNotifyFunc](JsState &jsState) {
1243 (*successNotifyFunc)(jsState, true);
1244 });
1245}
1246
1247QNapi::Value handleAbilityStageOnPrepareTerminationAsync(const CallbackInfo &cbInfo)
1248{
1249 qOhosPrintfInfo("%s", Q_FUNC_INFO);
1250
1251 return makeResolvedPromise(
1252 cbInfo.jsState().eval<QNapi::Number>(
1253 "@ohos.app.ability.AbilityConstant.PrepareTermination.TERMINATE_IMMEDIATELY"));
1254}
1255
1256void handleAbilityStageOnDestroy(const CallbackInfo &)
1257{
1258 qOhosPrintfInfo("AbilityStage::onDestroy: start");
1259
1260 qOhosPrintfInfo("AbilityStage::onDestroy: destroying the Qt thread object");
1262
1263 if (!s_exitCodeFilePath.empty()) {
1264 if (FILE *f = fopen(s_exitCodeFilePath.c_str(), "w")) {
1265 fprintf(f, "%d\n", s_appExitCode);
1266 fclose(f);
1267 }
1268 }
1269
1270 qOhosPrintfInfo("AbilityStage::onDestroy: calling _Exit(%d)", s_appExitCode);
1271 std::_Exit(s_appExitCode);
1272}
1273
1274void handleAbilityOnNewWant(const CallbackInfo &cbInfo)
1275{
1276 QNapi::Object qAbility;
1277 QNapi::Object want;
1278 QNapi::Object launchParam;
1279 cbInfo.getLeadingArgs(Q_FUNC_INFO, qAbility, want, launchParam);
1280
1281 qOhosPrintfDebug(
1282 "%s: input Want: %s, launchParam: %s",
1283 Q_FUNC_INFO, QNapi::toJsonString(want).c_str(), QNapi::toJsonString(launchParam).c_str());
1284
1285 tryDetectBrokenWant(cbInfo.jsState(), want);
1286
1287 if (!QAbilityInstancesManager::isQtInternalWantFromThisProcess(want))
1288 dispatchNewWant(want, launchParam);
1289 else
1290 qOhosPrintfDebug("%s: received qt-internal Want from current process, nothing to do", Q_FUNC_INFO);
1291}
1292
1293void loadWindowStageContentPage(JsState &jsState, QNapi::Object &qAbility, const QNapi::Object &windowStage)
1294{
1295 auto *jsEnv = jsState.env();
1296
1297 auto qAbilityInstanceId = getQAbilityInstancesManager().getQAbilityInstanceIdOrPendingAutoStartedId(qAbility);
1298 auto xComponentId = QXComponentId::createForNativeNodeMainWindow(qAbilityInstanceId);
1299
1300 auto qAbilityRef = moveToSharedPtr(QNapi::Reference<>::makePersistentFrom(qAbility));
1301 auto windowStageRef = moveToSharedPtr(QNapi::Reference<>::makePersistentFrom(windowStage));
1302
1303 auto localStorage = jsState.eval<QNapi::Object>("LocalStorage.makeNewLocalStorage()");
1304 localStorage.call(
1305 "setOrCreate",
1306 {
1307 "createInfo",
1308 QNapi::makeObject(
1309 jsEnv,
1310 {
1311 {"xComponentId", xComponentId.toNapiValue(jsEnv)},
1312 {"onDisAppear", []() {}},
1313 {
1314 "onAttach",
1315 [qAbilityRef, windowStageRef](const QtOhos::CallbackInfo &cbInfo) {
1316 getQAbilityInstancesManager().handleStartedUiInstance(
1317 cbInfo.jsState(), qAbilityRef->Value(), windowStageRef->Value());
1318 }},
1319 {
1320 "onAppear",
1321 [xComponentId]() {
1322 qOhosPrintfDebug("XComponentId: %s onAppear", xComponentId.stringId().c_str());
1323 }
1324 },
1325 }),
1326 });
1327 qAbility.set("localStorage", localStorage);
1328
1329 const std::string mainWindowNativeNodePagePath = "pages/MainWindowNativeNode";
1330 windowStage.evalToPromiseOrRejectOnThrow("loadContent(*)", {mainWindowNativeNodePagePath, localStorage})
1331 .onThen([qAbilityRef](const CallbackInfo &) {
1332 auto launchWant = qAbilityRef->eval<QNapi::Object>("launchWant");
1333 qOhosPrintfDebug("%s: launchWant: %s", Q_FUNC_INFO, QNapi::toJsonString(launchWant).c_str());
1334 })
1335 .onCatch([qAbilityInstanceId](const CallbackInfo &cbInfo) {
1336 QtOhos::logJsCallbackError(cbInfo, "windowStage.loadContent failed");
1337 qOhosReportFatalErrorAndAbort(
1338 "%s: Failed to loadContent for QAbility instance(qAbilityInstanceId: %s)", Q_FUNC_INFO, qAbilityInstanceId.c_str());
1339 });
1340}
1341
1342QNapi::Symbol getAbilityWindowStageCreatedOrRestoredPropSymbol(JsState &jsState)
1343{
1344 struct Symbol
1345 {
1346 };
1347 return jsState.getJsSymbolForType<Symbol>();
1348}
1349
1350void handleAbilityOnWindowStageCreate(const CallbackInfo &cbInfo)
1351{
1352 QNapi::Object qAbility;
1353 QNapi::Object windowStage;
1354 cbInfo.getLeadingArgs(Q_FUNC_INFO, qAbility, windowStage);
1355
1356 qAbility.set(getAbilityWindowStageCreatedOrRestoredPropSymbol(cbInfo.jsState()), true);
1357 loadWindowStageContentPage(cbInfo.jsState(), qAbility, windowStage);
1358}
1359
1360void handleAbilityOnWindowStageRestore(const CallbackInfo &cbInfo)
1361{
1362 QNapi::Object qAbility;
1363 QNapi::Object windowStage;
1364 cbInfo.getLeadingArgs(Q_FUNC_INFO, qAbility, windowStage);
1365
1366 auto optCreatedOrRestoredProp = QNapi::getOptionalPropOrEmpty<QNapi::Boolean>(
1367 qAbility, getAbilityWindowStageCreatedOrRestoredPropSymbol(cbInfo.jsState()));
1368 bool createdOrRestoredProp = !optCreatedOrRestoredProp.IsEmpty()
1369 ? optCreatedOrRestoredProp.Value()
1370 : false;
1371
1372 if (!createdOrRestoredProp) {
1373 qAbility.set(getAbilityWindowStageCreatedOrRestoredPropSymbol(cbInfo.jsState()), true);
1374 loadWindowStageContentPage(cbInfo.jsState(), qAbility, windowStage);
1375 } else {
1376 qOhosPrintfDebug(
1377 "%s: window page already created or restored. Skip page reloading", Q_FUNC_INFO);
1378 }
1379}
1380
1381void handleAbilityOnWindowStageDestroy(const CallbackInfo &)
1382{
1383}
1384
1385QNapi::Value handleAbilityOnPrepareToTerminate(const CallbackInfo &cbInfo)
1386{
1387 auto __dbg = make_QCScopedDebugJS(Q_FUNC_INFO);
1388 auto ability = cbInfo.getFirstArg<QNapi::Object>(Q_FUNC_INFO);
1389
1390 auto abilityPeer = cbInfo.jsState().tryGetQAbilityPeerByInstance(ability);
1391 if (!abilityPeer) {
1392 qOhosPrintfWarning("%s: unrecognized ability, returning immediately", Q_FUNC_INFO);
1393 return QNapi::Boolean::New(cbInfo.Env(), false);
1394 }
1395
1396 bool destroyAllowed = abilityPeer->destroyAllowedFlag()->load();
1397 qOhosPrintfDebug(
1398 "%s: ability id: '%s', destroyAllowed=%s",
1399 Q_FUNC_INFO, abilityPeer->instanceId().c_str(), mapBoolToTrueFalseStr(destroyAllowed));
1400
1401 if (!destroyAllowed) {
1402 QtOhos::invokeInQtThread(
1403 [qwindowRef = abilityPeer->qWindowRef()]() {
1404 auto *qwindow = qobject_cast<QWindow *>(qwindowRef.data());
1405 if (qwindow != nullptr) {
1406 qOhosPrintfInfo("handleAbilityOnPrepareToTerminate: calling QWindow::close()");
1407 QOhosCloseEventContext::runWithCloseRootCauseSet(
1408 QOhosCloseEventContext::CloseRootCause::OnPrepareToTerminate,
1409 [&]() {
1410 qwindow->close();
1411 });
1412 } else {
1413 qOhosPrintfDebug("%s: QWindow is null", Q_FUNC_INFO);
1414 }
1415 });
1416 }
1417
1418 return QNapi::Boolean::New(cbInfo.Env(), !destroyAllowed);
1419}
1420
1421QNapi::Value handleAbilityOnPrepareToTerminateAsync(const CallbackInfo &cbInfo)
1422{
1423 auto __dbg = make_QCScopedDebugJS(Q_FUNC_INFO);
1424
1425 auto qAbility = cbInfo.getFirstArg<QNapi::Object>(Q_FUNC_INFO);
1426
1427 JsState &jsState = cbInfo.jsState();
1428
1429 auto optQUiAbilityPeer = QUiAbilityPeer::tryCastFromQAbilityPeerOrNull(
1430 jsState.tryGetQAbilityPeerByInstance(qAbility));
1431
1432 if (optQUiAbilityPeer) {
1433 return getQAbilityInstancesManager().getAbilityPeerBackend(optQUiAbilityPeer)->handleCloseRequestFromSystem(
1434 jsState, "UIAbility::onPrepareToTerminateAsync",
1436 [](JsState &jsState, QUiAbilityPeerBackend::CloseAbilityRequestResolution ohosRequestResolution) {
1437 return QNapi::Boolean::New(
1438 jsState.env(),
1439 ohosRequestResolution == QUiAbilityPeerBackend::CloseAbilityRequestResolution::DontClose);
1440 });
1441 } else {
1442 qOhosPrintfWarning("%s: no matching QAbilityPeer, resolving immediately with 'false'", Q_FUNC_INFO);
1443 return makeResolvedPromise(QNapi::Boolean::New(cbInfo.Env(), false));
1444 }
1445}
1446
1447void handleAbilityOnCreate(const CallbackInfo &cbInfo)
1448{
1449 auto __dbg = make_QCScopedDebugJS(Q_FUNC_INFO);
1450
1451 QNapi::Object ability;
1452 QNapi::Object want;
1453 QNapi::Object launchParam;
1454 cbInfo.getLeadingArgs(Q_FUNC_INFO, ability, want, launchParam);
1455
1456 QAbilityInstancesManager::setLaunchParamOnAbilityObject(cbInfo.jsState(), ability, launchParam);
1457}
1458
1459QNapi::Value handleAbilityOnDestroy(const CallbackInfo &cbInfo)
1460{
1461 auto __dbg = make_QCScopedDebugJS(Q_FUNC_INFO);
1462
1463 auto qAbility = cbInfo.getFirstArg<QNapi::Object>(Q_FUNC_INFO);
1464
1465 auto qAbilityPeer = cbInfo.jsState().tryGetQAbilityPeerByInstance(qAbility);
1466 if (!qAbilityPeer) {
1467 qOhosPrintfDebug("%s: no matching QAbilityPeer, returning resolved Promise", Q_FUNC_INFO);
1468 return makeResolvedPromise(cbInfo.Env().Undefined());
1469 }
1470
1471 auto optQWindowDestroyPromise = qAbilityPeer->qWindowDestroyPromise();
1472
1473 auto initialPromise = optQWindowDestroyPromise.has_value()
1474 ? optQWindowDestroyPromise.value()
1475 : makeResolvedPromise(cbInfo.Env().Undefined());
1476
1477 auto resultPromiseDeferred = std::make_shared<QNapi::Promise::Deferred>(cbInfo.Env());
1478
1479 initialPromise.onFinally(
1480 [qAbilityPeer, resultPromiseDeferred](const CallbackInfo &cbInfo) {
1481 qOhosPrintfDebug("%s: initial Promise resolved for id='%s'", Q_FUNC_INFO, qAbilityPeer->instanceId().c_str());
1482
1483 QtOhos::removeMatchingJsQAbilityPeer(qAbilityPeer->qAbility());
1484
1485 if (cbInfo.jsState().defaultQAbilityPeer()->qAbility().IsEmpty()) {
1487
1488 qOhosPrintfInfo("Qt: requested Qt app quit, waiting for the main() function to return");
1489
1490 constexpr auto resultPromiseAutoresolveTimeout = 5s;
1491
1492 asyncRunTaskInTemporaryThreadWithTimeout(
1493 cbInfo.jsState(),
1494 []() {
1496 },
1497 resultPromiseAutoresolveTimeout,
1498 [instanceId = qAbilityPeer->instanceId(), resultPromiseDeferred](JsState &jsState, bool threadExited) {
1499 qOhosPrintfInfo(
1500 "Qt: end waiting for Qt app's main() function, returned: %s",
1501 mapBoolToTrueFalseStr(threadExited));
1502 qOhosPrintfDebug("%s: resolving result Promise for id='%s'", Q_FUNC_INFO, instanceId.c_str());
1503 resultPromiseDeferred->Resolve(Napi::Env(jsState.env()).Undefined());
1504 });
1505 } else {
1506 resultPromiseDeferred->Resolve(cbInfo.Env().Undefined());
1507 }
1508 });
1509
1510 qOhosPrintfDebug("%s: returning Promise for id='%s'", Q_FUNC_INFO, qAbilityPeer->instanceId().c_str());
1511
1512 return resultPromiseDeferred->Promise();
1513}
1514
1515void initDeviceInfo(JsState &jsState)
1516{
1517 using Type = QOhosDeviceInfo::Type;
1518
1519 static const std::pair<Type, const char *> strPropertiesMap[] = {
1520 {Type::deviceType, "deviceType"},
1521 {Type::manufacture, "manufacture"},
1522 {Type::brand, "brand"},
1523 {Type::marketName, "marketName"},
1524 {Type::productSeries, "productSeries"},
1525 {Type::productModel, "productModel"},
1526 {Type::softwareModel, "softwareModel"},
1527 {Type::hardwareModel, "hardwareModel"},
1528 {Type::hardwareProfile, "hardwareProfile"},
1529 {Type::serial, "serial"},
1530 {Type::bootloaderVersion, "bootloaderVersion"},
1531 {Type::abiList, "abiList"},
1532 {Type::securityPatchTag, "securityPatchTag"},
1533 {Type::displayVersion, "displayVersion"},
1534 {Type::incrementalVersion, "incrementalVersion"},
1535 {Type::osReleaseType, "osReleaseType"},
1536 {Type::osFullName, "osFullName"},
1537 {Type::versionId, "versionId"},
1538 {Type::buildType, "buildType"},
1539 {Type::buildUser, "buildUser"},
1540 {Type::buildHost, "buildHost"},
1541 {Type::buildTime, "buildTime"},
1542 {Type::buildRootHash, "buildRootHash"},
1543 {Type::udid, "udid"},
1544 {Type::distributionOSName, "distributionOSName"},
1545 {Type::distributionOSVersion, "distributionOSVersion"},
1546 {Type::distributionOSReleaseType, "distributionOSReleaseType"},
1547 };
1548
1549 static const std::pair<Type, const char *> intPropertiesMap[] = {
1550 {Type::majorVersion, "majorVersion"},
1551 {Type::seniorVersion, "seniorVersion"},
1552 {Type::featureVersion, "featureVersion"},
1553 {Type::buildVersion, "buildVersion"},
1554 {Type::sdkApiVersion, "sdkApiVersion"},
1555 {Type::firstApiVersion, "firstApiVersion"},
1556 {Type::distributionOSApiVersion, "distributionOSApiVersion"},
1557 };
1558
1559 auto deviceInfoObj = jsState.eval<QNapi::Object>("@ohos.deviceInfo");
1560
1561 QMap<Type, QVariant> deviceInfo;
1562 for (const auto &propEntry : strPropertiesMap)
1563 deviceInfo[propEntry.first] = QString::fromStdString(deviceInfoObj.get<QNapi::String>(propEntry.second));
1564 for (const auto &propEntry : intPropertiesMap)
1565 deviceInfo[propEntry.first] = static_cast<int>(deviceInfoObj.get<QNapi::Number>(propEntry.second));
1566
1567 QOhosDeviceInfo::init(std::move(deviceInfo));
1568}
1569
1570void initAppData(JsState &jsState, QNapi::Object appContext)
1571{
1572 initDeviceInfo(jsState);
1573
1574 auto systemLocaleId = QString::fromStdString(
1575 jsState.eval<QNapi::String>("@ohos.intl.Locale<new>().toString()"));
1576 auto systemPreferredLanguages = QNapi::getArrayElements<QStringList, QNapi::String>(
1577 jsState.eval<QNapi::Array>("@ohos.i18n.System.getPreferredLanguageList()"),
1578 &QString::fromStdString);
1579 QtOhos::invokeInQtThread(
1580 [systemLocaleId, systemPreferredLanguages]() {
1581 QOhosPlatformIntegration::setSystemLocale(new QOhosSystemLocale(systemLocaleId, systemPreferredLanguages));
1582 });
1583
1585 appContext.call("setColorMode", {jsState.mapOhosEnumToJs(defaultColorMode)});
1586}
1587
1588std::string buildFcLangEnvVariableValue(JsState &jsState)
1589{
1590 std::string language = jsState.eval<QNapi::String>("@ohos.intl.Locale<new>().language");
1591 std::string region = jsState.eval<QNapi::String>("@ohos.intl.Locale<new>().region");
1592
1593 return language + "_" + region + ".UTF-8";
1594}
1595
1596std::shared_ptr<QAbilityEngine> makeAbilityEngineForQtRunMode(QtRunMode qtRunMode)
1597{
1598 switch (qtRunMode) {
1599 case QtRunMode::Normal:
1601 return std::make_shared<QUiAbilityEngine>();
1602 }
1603
1604 qOhosReportFatalErrorAndAbort("%s: Invalid qtRunMode: %d", Q_FUNC_INFO, static_cast<int>(qtRunMode));
1605}
1606
1607void setupQtApplicationImpl(JsState &jsState, QNapi::Object appStartupObj, QtRunMode qtRunMode)
1608{
1609 auto appContext = appStartupObj.get<QNapi::Object>("appContext");
1610 auto appContextDirs = AppContextDirs::mapFromNapiObject(appContext);
1611 if (appContextDirs.resourceDir.empty()) {
1612 auto optResourceDirProp = QNapi::getOptionalPropOrEmpty<QNapi::String>(appStartupObj, "resourceDir");
1613 std::string resourceDir = !optResourceDirProp.IsEmpty() ? optResourceDirProp : std::string();
1614 appContextDirs.resourceDir = !resourceDir.empty()
1615 ? resourceDir
1616 : appContextDirs.bundleCodeDir + "/entry/resources/resfile";
1617 }
1618
1619 auto jsModulesFactories = appStartupObj.get<QNapi::Object>("modulesFactories");
1620
1621 qOhosPrintfDebug("%s: setting up Qt in %s mode", Q_FUNC_INFO, mapQtRunModeToString(qtRunMode));
1622
1623 getQAbilityInstancesManagerPtr() =
1624 makeQAbilityInstancesManager(
1625 makeAbilityEngineForQtRunMode(qtRunMode),
1626 &handleDefaultQAbilityInstanceStartup);
1627
1628 currentQtRunMode = qtRunMode;
1630 jsModulesFactories.Env(), makeJsModulesFactoriesMap(jsModulesFactories),
1631 std::make_shared<AppFunctionsImpl>(), qtRunMode);
1632
1633 QOhosAppContext::init(appContextDirs.mapToQOhosAppContextProperties());
1634 initAppData(jsState, appContext);
1635
1636 const auto ohosSdkApiVersion = QOhosDeviceInfo::sdkApiVersion();
1637 if (ohosSdkApiVersion < minSupportedOhosSdkApiVersion) {
1638 qOhosReportFatalErrorAndAbort(
1639 "%s: unsupported OHOS version! Current API version: %d, minimum supported version: %d. Aborting.",
1640 Q_FUNC_INFO, ohosSdkApiVersion, minSupportedOhosSdkApiVersion);
1641 }
1642
1643 const auto recognizedDeviceType = QOhosDeviceInfo::tryGetRecognizedDeviceType();
1644 if (!recognizedDeviceType.has_value()) {
1645 qOhosReportFatalErrorAndAbort(
1646 "%s: Unrecognized device type: %s. Qt does not support unrecognized devices. Aborting.",
1647 Q_FUNC_INFO, qPrintable(QOhosDeviceInfo::getProperty(QOhosDeviceInfo::Type::deviceType).toString()));
1648 }
1649
1650 bool allowUnsupportedDevices =
1651 qEnvironmentVariable("QT_IO_EXPERIMENTAL_ALLOW_UNSUPPORTED_DEVICES", {})
1652 == QLatin1String("true");
1653 if (!QOhosDeviceInfo::isCurrentDeviceSupported() && !allowUnsupportedDevices) {
1654 qOhosReportFatalErrorAndAbort(
1655 "%s: Unsupported device type: %s. Aborting.",
1656 Q_FUNC_INFO, qPrintable(QOhosDeviceInfo::getProperty(QOhosDeviceInfo::Type::deviceType).toString()));
1657 }
1658
1659 s_appSharedLibName = appStartupObj.get<QNapi::String>("appName");
1660
1661 qOhosPrintfDebug("setupQtApplication() - sharedLibraryName: %s", s_appSharedLibName.c_str());
1662 qOhosPrintfDebug("setupQtApplication() - bundleCodeDir: %s", appContextDirs.bundleCodeDir.c_str());
1663
1664 s_appSharedLibsDirPath = appContextDirs.bundleCodeDir + targetLibraryDirectory();
1665 qOhosPrintfDebug("setupQtApplication() - Shared libraries directory: %s", s_appSharedLibsDirPath.c_str());
1666
1667 QByteArrayList qmls = { QByteArray::fromStdString(appContextDirs.resourceDir + "/qml") };
1668
1669 auto jsAppArgs = QNapi::getPropOrUndefined(appStartupObj, "appArgs");
1670 if (jsAppArgs.IsArray()) {
1671 s_appArgs = std::make_unique<std::vector<std::string>>(
1672 QNapi::getArrayElements<std::vector<std::string>, QNapi::String>(
1673 QNapi::checkedCast<QNapi::Array>(jsAppArgs)));
1674 }
1675
1676 struct {
1677 const char *variable;
1678 std::string value;
1679 } env_variables[] = {
1680 {"QT_QPA_PLATFORM_PLUGIN_PATH", s_appSharedLibsDirPath },
1681 {"QT_QPA_PLATFORMTHEME", ohosThemeName},
1682 {"QT_QPA_PLATFORM", "ohos"},
1683 {"QML_DISABLE_DISK_CACHE", "1"},
1684 {"QT_PLUGIN_PATH", s_appSharedLibsDirPath },
1685 {"QML2_IMPORT_PATH", qmls.join(":").toStdString() },
1686 // FIXME: temporary measure for preventing QtQuick2-based apps from crashing
1687 {"QV4_FORCE_INTERPRETER", "1"},
1688 {"QT_PRINTER_MODULE", "ohosprintersupport"},
1689 {"TMPDIR", QOhosAppContext::getProperty(QOhosAppContext::Type::tempDir).toStdString()},
1690 {"HOME", QOhosAppContext::getProperty(QOhosAppContext::Type::filesDir).toStdString()},
1691 {"FC_LANG", buildFcLangEnvVariableValue(jsState)},
1692 };
1693
1694 for (const auto &e : env_variables) {
1695 if (::setenv(e.variable, e.value.c_str(), 1) != 0) {
1696 throw std::runtime_error(
1697 QString::fromUtf8("Cannot set %1 environment variable").arg(QString::fromUtf8(e.variable)).toStdString());
1698 }
1699 }
1700
1701 if (::chdir(appContextDirs.filesDir.c_str()) != 0) {
1702 auto chdirErrno = errno;
1703 qOhosPrintfWarning(
1704 "%s: failed to change current directory to '%s': %s",
1705 Q_FUNC_INFO, appContextDirs.filesDir.c_str(), std::strerror(chdirErrno));
1706 }
1707}
1708
1709QtRunMode getQtRunModeFromAppStartupObj(QNapi::Object appStartupObj)
1710{
1711 std::unordered_map<std::string, QtRunMode> abilityClassNameToQtRunModeMap = {
1712 {"QAbility", QtRunMode::Normal},
1713 };
1714
1715 const auto optAbilityClassName = QNapi::getOptionalPropOrEmpty<QNapi::String>(appStartupObj, "abilityClassName");
1716 if (!optAbilityClassName.IsEmpty()) {
1717 const std::string abilityClassName = optAbilityClassName;
1718 if (abilityClassNameToQtRunModeMap.find(abilityClassName) != abilityClassNameToQtRunModeMap.end())
1719 return abilityClassNameToQtRunModeMap[abilityClassName];
1720
1721 qOhosReportFatalErrorAndAbort(
1722 "%s: got unsupported name of the Ability class: '%s'", Q_FUNC_INFO, abilityClassName.c_str());
1723 }
1724
1725 return QtRunMode::Normal;
1726}
1727
1728void setupQtApplication(const CallbackInfo &cbInfo)
1729{
1730 auto appStartupObj = cbInfo.getFirstArg<QNapi::Object>(Q_FUNC_INFO);
1731 auto qtRunMode = getQtRunModeFromAppStartupObj(appStartupObj);
1732 setupQtApplicationImpl(cbInfo.jsState(), appStartupObj, qtRunMode);
1733}
1734
1735void runQtChildProcess(const CallbackInfo &cbInfo)
1736{
1737 auto __dbg = make_QCScopedDebugJS(Q_FUNC_INFO);
1738
1739 auto paramsObj = cbInfo.getFirstArg<QNapi::Object>(Q_FUNC_INFO);
1740
1742
1743 auto childSetupJson = readChildProcessSetupData();
1744 auto childSetupObj = QNapi::checkedCast<QNapi::Object>(
1745 QOhosJsEnv::toNapiValue(cbInfo.Env(), childSetupJson));
1746 childSetupObj["modulesFactories"] = paramsObj.get<QNapi::Object>("modulesFactories");
1747
1748 setupQtApplicationImpl(cbInfo.jsState(), childSetupObj, QtRunMode::NoUiChildProcess);
1749
1750 auto appArgs = s_appArgs ? *s_appArgs : std::vector<std::string>();
1751
1752 QThread::currentThread();
1753 QtOhos::initQtThreadState();
1754 auto appMainFuncLauncher = makeAppMainFuncLauncher(
1755 {
1756 .appLibraryPath = s_appSharedLibsDirPath + "/" + s_appSharedLibName,
1757 },
1758 [](int) {
1759 });
1760 appMainFuncLauncher(appArgs);
1761}
1762
1763QNapi::Value makeXComponentIdForMainWindowWithQAbilityInstanceId(const CallbackInfo &cbInfo)
1764{
1765 std::string qAbilityInstanceId = cbInfo.getFirstArg<QNapi::String>(Q_FUNC_INFO);
1766 return QXComponentId::createForNativeNodeMainWindow(qAbilityInstanceId).toNapiValue(cbInfo.Env());
1767}
1768
1769QNapi::Value checkIsAdapterCApiSupported(const CallbackInfo &cbInfo)
1770{
1771 constexpr bool adapterCApiSupported = true;
1772 return QNapi::Boolean::New(cbInfo.Env(), adapterCApiSupported);
1773}
1774
1775}
1776
1778{
1779 return currentQtRunMode == QtRunMode::NoUiChildProcess;
1780}
1781
1783{
1784 return vsyncOnSoftwareBackingStoreEnabled;
1785}
1786
1788{
1789 auto __dbg = make_QCScopedDebugJS("quitApplicationFromJsThread");
1790 auto hotStartIterationToQuit = s_hotStartIteration.lastRequestedInJsThread;
1791 QtOhos::invokeInQtThread(
1792 [hotStartIterationToQuit]() {
1793 if (s_hotStartIteration.activeInQtThread == hotStartIterationToQuit)
1794 QCoreApplication::quit();
1795 });
1796}
1797
1799{
1800 qOhosDebug(QtForOhos) << "QOhos updateApplicationState" << state;
1801
1803 return;
1804
1805 if (state <= Qt::ApplicationInactive) {
1806 // NOTE: sometimes we will receive two consecutive suspended notifications,
1807 // In the second suspended notification, QWindowSystemInterface::flushWindowSystemEvents()
1808 // will deadlock since the dispatcher has been stopped in the first suspended notification.
1809 // To avoid the deadlock we simply return if we found the event dispatcher has been stopped.
1811 return;
1812
1813 // Don't send timers and sockets events anymore if we are going to hide all windows
1815 QWindowSystemInterface::handleApplicationStateChanged(Qt::ApplicationState(state));
1816 } else {
1818 QWindowSystemInterface::handleApplicationStateChanged(Qt::ApplicationState(state));
1820 }
1821}
1822
1824{
1825 static bool block = qEnvironmentVariableIntValue("QT_BLOCK_EVENT_LOOPS_WHEN_SUSPENDED") != 0;
1826 return block;
1827}
1828
1830{
1831 return experimentalEnableGlBackinStore;
1832}
1833
1835{
1836 return debugDrawQtRasterBackingStoreFlushedRegion;
1837}
1838
1840{
1841 return debugUseBasicStyleAndTheme;
1842}
1843
1845{
1846 return enableNativeNodeApiKeyEvents;
1847}
1848
1850{
1851 return enableNativeNodeApiMouseEvents;
1852}
1853
1858
1859}
1860
1861QT_END_NAMESPACE
1862
1863EXTERN_C_START
1864
1865static napi_value Init(napi_env env, napi_value exports)
1866{
1867 auto __dbg = make_QCScopedDebugJS("qohosjsmain Init");
1868
1869 QNapi::Object(env, exports).DefineProperties(
1870 {
1871 Napi::PropertyDescriptor::Function("handleAbilityStageOnCreate", QtOhos::handleAbilityStageOnCreate),
1872 Napi::PropertyDescriptor::Function("handleAbilityStageOnNewProcessRequest", QtOhos::handleAbilityStageOnNewProcessRequest),
1873 Napi::PropertyDescriptor::Function("handleAbilityStageOnAcceptWant", QtOhos::handleAbilityStageOnAcceptWant),
1874 Napi::PropertyDescriptor::Function("handleAbilityStageOnPrepareTerminationAsync", QtOhos::handleAbilityStageOnPrepareTerminationAsync),
1875 Napi::PropertyDescriptor::Function("handleAbilityStageOnDestroy", QtOhos::handleAbilityStageOnDestroy),
1876 Napi::PropertyDescriptor::Function("handleAbilityOnNewWant", QtOhos::handleAbilityOnNewWant),
1877 Napi::PropertyDescriptor::Function("handleAbilityOnWindowStageCreate", QtOhos::handleAbilityOnWindowStageCreate),
1878 Napi::PropertyDescriptor::Function("handleAbilityOnWindowStageRestore", QtOhos::handleAbilityOnWindowStageRestore),
1879 Napi::PropertyDescriptor::Function("handleAbilityOnWindowStageDestroy", QtOhos::handleAbilityOnWindowStageDestroy),
1880 Napi::PropertyDescriptor::Function("onStageDestroy", QtOhos::handleAbilityOnWindowStageDestroy),
1881 Napi::PropertyDescriptor::Function("handleAbilityOnPrepareToTerminate", QtOhos::handleAbilityOnPrepareToTerminate),
1882 Napi::PropertyDescriptor::Function("handleAbilityOnPrepareToTerminateAsync", QtOhos::handleAbilityOnPrepareToTerminateAsync),
1883 Napi::PropertyDescriptor::Function("handleAbilityOnCreate", QtOhos::handleAbilityOnCreate),
1884 Napi::PropertyDescriptor::Function("handleAbilityOnDestroy", QtOhos::handleAbilityOnDestroy),
1885 Napi::PropertyDescriptor::Function("handleAbilityOnBackground", QtOhos::handleAbilityOnBackground),
1886 Napi::PropertyDescriptor::Function("handleAbilityOnContinue", QtOhos::handleAbilityOnContinue),
1887 Napi::PropertyDescriptor::Function("onBackground", QtOhos::handleAbilityOnBackground),
1888 Napi::PropertyDescriptor::Function("handleAbilityOnForeground", QtOhos::handleAbilityOnForeground),
1889 Napi::PropertyDescriptor::Function("onForeground", QtOhos::handleAbilityOnForeground),
1890 Napi::PropertyDescriptor::Function("setupQtApplication", QtOhos::setupQtApplication),
1891 Napi::PropertyDescriptor::Function("runQtChildProcess", QtOhos::runQtChildProcess),
1892 Napi::PropertyDescriptor::Function(
1893 "makeXComponentIdForMainWindowWithQAbilityInstanceId",
1894 QtOhos::makeXComponentIdForMainWindowWithQAbilityInstanceId),
1895 Napi::PropertyDescriptor::Function("checkIsAdapterCApiSupported", QtOhos::checkIsAdapterCApiSupported),
1896 });
1897
1898 // Here put elements that
1899 QArkUi::QXComponentRegistry::Init(env, exports);
1900
1901 return exports;
1902}
1903
1904EXTERN_C_END
1905
1906extern "C" __attribute__((constructor)) void RegisterEntryModule(void)
1907{
1908 static napi_module qtMainModule = {
1909 .nm_version = 1,
1910 .nm_flags = 0,
1911 .nm_filename = nullptr,
1912 .nm_register_func = Init,
1913 .nm_modname = "qohos",
1914 .nm_priv = nullptr,
1915 .reserved = {nullptr},
1916 };
1917
1918 napi_module_register(&qtMainModule);
1919}
static QOhosEventDispatcherStopper * instance()
static QOhosPlatformIntegration * instance()
static QXComponentId createForNativeNodeMainWindow(const std::string &qAbilityInstanceId)
JsState & jsState() const
virtual void visitEachQAbilityPeer(const std::function< void(std::shared_ptr< QAbilityPeer >)> &visitor)=0
static std::shared_ptr< QUiAbilityPeer > tryCastFromQAbilityPeerOrNull(std::shared_ptr< QAbilityPeer > qAbilityPeer)
bool isCurrentDeviceSupported()
void init(QMap< Type, QVariant > devinfo)
Combined button and popup list for selecting options.
std::shared_ptr< void > makeWatchdog()
bool acquireAndCleanPendingAutoStartedInstanceWindowFlag()
std::string const char * mapBoolToTrueFalseStr(bool value)
bool blockEventLoopsWhenSuspended()
void removeMatchingJsQAbilityPeer(QNapi::Object qAbility)
void updateApplicationState(int state)
bool isOhosNoUiChildMode()
void initJsThreadState(napi_env env, std::map< std::string, QNapi::Reference< QNapi::Function > > &&jsModulesFactories, std::shared_ptr< AppFunctions > appFunctions, QtRunMode qtRunMode)
bool isGlBackingStoreDefaultEnabled()
void quitApplicationFromJsThread()
bool isVsyncOnSoftwareBackingStoreEnabled()
bool isNativeNodeApiMouseEventsEnabled()
bool isNativeNodeApiKeyEventsEnabled()
bool isDebugUseBasicStyleAndThemeEnabled()
bool isDebugDrawQtRasterBackingStoreFlushedRegionEnabled()
enums::ohos::app::ability::AbilityConstant::OnContinueResult QOhosAbilityOnContinueResult
static bool s_hotStartEnabled
QOhosOptional< std::uint64_t > activeInQtThread
static bool s_autoStartedAbilityInstanceWaitingForQtWindow
QT_END_NAMESPACE static EXTERN_C_START napi_value Init(napi_env env, napi_value exports)
std::function< void()> s_qtAppThreadIdleStateWaitFunc
static std::string s_appSharedLibName
static QList< QByteArray > s_applicationParams
static std::unique_ptr< std::vector< std::string > > s_appArgs
int(* Main)(int, char **)
static int s_appExitCode
QOhosOptional< std::uint64_t > lastRequestedInJsThread
static std::string s_appSharedLibsDirPath
static std::string s_exitCodeFilePath
QOhosConsumer< std::vector< std::string > > s_qtAppThreadMainFuncLauncher
std::nullopt_t makeEmptyQOhosOptional()
virtual std::shared_ptr< QUiAbilityPeerBackend > getAbilityPeerBackend(std::shared_ptr< QUiAbilityPeer > uiAbilityPeer)=0