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