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