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