Revision control
++ // Copyright (C) 2025 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
++
++ #ifndef QCORE_OHOS_P_H
++ #define QCORE_OHOS_P_H
++
++ //
++ // W A R N I N G
++ // -------------
++ //
++ // This file is not part of the Qt API. It exists purely as an
++ // implementation detail. This header file may change from version to
++ // version without notice, or even be removed.
++ //
++ // We mean it.
++ //
++
++ #include <QtCore/private/qnapi_p.h>
++ #include <QtCore/private/qohoscommon_p.h>
++ #include <QtCore/qglobal.h>
++ #include <QtCore/qobject.h>
++ #include <QtCore/qpointer.h>
++ #include <QtCore/qmutex.h>
++ #include <algorithm>
++ #include <cstdint>
++ #include <functional>
++ #include <map>
++ #include <memory>
++ #include <optional>
++ #include <pthread.h>
++ #include <string>
++ #include <tuple>
++ #include <type_traits>
++ #include <typeinfo>
++ #include <vector>
++
++ QT_BEGIN_NAMESPACE
++
++ namespace QtOhos {
++
++ class QObjectThreadSafeRef;
++
++ template<typename Enum>
++ struct OhosEnumMeta
++ {
++ };
++
++ }
++
++ class Q_CORE_EXPORT QOhosJsState
++ {
++ public:
++ QOhosJsState(const QOhosJsState &) = delete;
++ QOhosJsState &operator=(const QOhosJsState &) = delete;
++
++ virtual ~QOhosJsState();
++
++ virtual napi_env env() = 0;
++
++ virtual QNapi::Object defaultWindowStageOrEmpty() = 0;
++ virtual QNapi::Object defaultUiContextOrEmpty() = 0;
++
++ virtual std::optional<QNapi::Object> tryGetJsWindowByQWindow(QtOhos::QObjectThreadSafeRef qwindow) = 0;
++ virtual std::optional<QNapi::Object> tryGetQAbilityByQWindow(QtOhos::QObjectThreadSafeRef qwindow) = 0;
++ virtual std::optional<QNapi::Object> defaultQAbility() = 0;
++
++ virtual QNapi::Object appLaunchWant() = 0;
++ virtual std::optional<QNapi::Object> optAppLaunchParam() = 0;
++
++ // a registered module, or an unknown one loaded with napi_load_module() and memoized; empty if that load fails
++ virtual std::optional<QNapi::Object> tryGetModule(const std::string &moduleName) = 0;
++
++ template<typename T>
++ std::enable_if_t<std::is_default_constructible<T>::value, T> &getAttachedObjectWithLazyCreate();
++
++ virtual void startAppProcess(
++ const std::string &processId, QNapi::Object requestWant,
++ QNapi::Object optStartOptions, std::function<void(QOhosJsState &)> continueFunc) = 0;
++
++ virtual void startNoUiChildProcess(const std::string &libraryName, const std::vector<std::string> &args) = 0;
++
++ // wantConsumer's parameters: jsState, qAbility, want, launchParam
++ [[nodiscard]] virtual std::shared_ptr<void> registerNewWantConsumer(
++ QOhosConsumer<QOhosJsState &, QNapi::Object, QNapi::Object, QNapi::Object> wantConsumer) = 0;
++
++ // requestsHandler's parameters: jsState, wantParams, resultConsumer
++ virtual void setOnContinueRequestsHandler(
++ QNapi::Object qAbility,
++ std::function<void(QOhosJsState &, QNapi::Object, QOhosConsumer<QOhosJsState &, QNapi::Number>)> requestsHandler) = 0;
++
++ virtual void setDestroyFromSystemAllowed(QNapi::Object qAbility, bool destroyAllowed) = 0;
++
++ template<typename T = QNapi::Value>
++ T eval(const std::string &expr, const std::vector<QNapi::ValueWrapper> &exprArgs = {});
++
++ QNapi::Promise evalToPromiseOrRejectOnThrow(
++ const std::string &expr, const std::vector<QNapi::ValueWrapper> &exprArgs = {});
++
++ template<typename Enum>
++ QNapi::Number mapOhosEnumToJs(Enum enumValue);
++
++ template<typename Enum>
++ std::optional<Enum> tryMapOhosEnumFromJs(QNapi::Number enumJsValue);
++
++ template<typename Enum>
++ Enum mapOhosEnumFromJs(QNapi::Number enumJsValue);
++
++ protected:
++ struct OhosEnumInfo
++ {
++ std::string fullTypeName;
++ std::vector<std::pair<int, const char *>> enumeratorsNames;
++ };
++
++ QOhosJsState();
++
++ private:
++ template<typename Enum, typename = void>
++ struct OhosEnumFullTypeNameFetcher
++ {
++ static std::string fullTypeName()
++ {
++ return QtOhos::OhosEnumMeta<Enum>::moduleName + std::string(".") + QtOhos::OhosEnumMeta<Enum>::typeName;
++ }
++ };
++
++ template<typename Enum>
++ struct OhosEnumFullTypeNameFetcher<Enum, decltype(static_cast<void>(&QtOhos::OhosEnumMeta<Enum>::fullTypeName))>
++ {
++ static std::string fullTypeName()
++ {
++ return QtOhos::OhosEnumMeta<Enum>::fullTypeName;
++ }
++ };
++
++ template<typename Enum>
++ static OhosEnumInfo makeOhosEnumInfo();
++
++ virtual std::tuple<QNapi::Object, std::string> extractModuleFromEvalExpr(const std::string &expr) = 0;
++ virtual void *getAttachedObjectWithLazyCreate(
++ const std::type_info &objectTypeInfo, QOhosSupplier<std::shared_ptr<void>> objectFactory) = 0;
++ virtual QNapi::Number mapOhosEnumToJs(int enumValue, const std::type_info &enumTypeInfo, OhosEnumInfo (*ohosEnumInfoFactory)()) = 0;
++ virtual std::optional<int> tryMapOhosEnumFromJs(QNapi::Number enumJsValue, const std::type_info &enumTypeInfo, OhosEnumInfo (*ohosEnumInfoFactory)()) = 0;
++ virtual int mapOhosEnumFromJs(QNapi::Number enumJsValue, const std::type_info &enumTypeInfo, OhosEnumInfo (*ohosEnumInfoFactory)()) = 0;
++ };
++
++ class Q_CORE_EXPORT QOhosCallbackInfo : public QNapi::CallbackInfo
++ {
++ public:
++ using QNapi::CallbackInfo::CallbackInfo;
++
++ QOhosJsState &jsState() const;
++ };
++
++ namespace QOhosJsThreadGateway {
++
++ Q_CORE_EXPORT void invoke(std::function<void(QOhosJsState &)> &&task);
++
++ Q_CORE_EXPORT void invokeAndWaitForContinue(
++ std::function<void(QOhosJsState &, QOhosTaskPromise<>)> &&task,
++ std::string callerContextName = {});
++
++ Q_CORE_EXPORT void runAndWait(
++ const std::function<void(QOhosJsState &)> &task,
++ std::string callerContextName = {});
++
++ template<typename Func>
++ auto eval(Func &&func, std::string callerContextName = {})
++ -> decltype(func(std::declval<QOhosJsState &>()));
++
++ template<typename T>
++ T evalWithPromise(
++ std::function<void(QOhosJsState &, QOhosTaskPromise<T>)> evalFunc,
++ std::string callerContextName = {});
++
++ // Backward-compatible alias for evalWithPromise(); kept so existing callers
++ // passing a result consumer keep compiling.
++ template<typename T>
++ T evalWithConsumer(std::function<void(QOhosJsState &, std::function<void(T)>)> evalFunc);
++
++ }
++
++ class Q_CORE_EXPORT QOhosJsThreadOps
++ {
++ public:
++ virtual ~QOhosJsThreadOps();
++
++ virtual QOhosJsState &jsState() = 0;
++
++ virtual void invoke(std::function<void(QOhosJsState &)> &&task) = 0;
++ virtual void invokeAndWaitForContinue(
++ std::function<void(QOhosJsState &, QOhosTaskPromise<>)> &&task,
++ std::string callerContextName) = 0;
++ virtual void runAndWait(
++ const std::function<void(QOhosJsState &)> &task,
++ std::string callerContextName) = 0;
++
++ static void registerInstance(QOhosJsThreadOps *ops);
++ static QOhosJsThreadOps &instance();
++
++ protected:
++ QOhosJsThreadOps();
++ };
++
++ namespace QtOhos {
++
++ class Q_CORE_EXPORT QObjectThreadSafeRef
++ {
++ public:
++ QObjectThreadSafeRef();
++ QObjectThreadSafeRef(QPointer<QObject> obj);
++
++ QObjectThreadSafeRef(const QObjectThreadSafeRef &other);
++ QObjectThreadSafeRef &operator=(const QObjectThreadSafeRef &other);
++
++ bool operator==(const QObjectThreadSafeRef &other) const;
++ bool operator!=(const QObjectThreadSafeRef &other) const;
++
++ std::string refName() const;
++
++ // it may be called only inside Qt thread that created the reference
++ QPointer<QObject> data() const;
++
++ void visitInQtThreadIfAlive(std::function<void(QObject &)> &&visitFunc) const;
++
++ private:
++ struct QObjectRef
++ {
++ QPointer<QObject> obj;
++ std::string refName;
++ };
++
++ Q_CONSTINIT inline static QBasicMutex refsMapMutex;
++ Q_CONSTINIT inline static std::uint64_t refsMapInsertCounter = 0;
++
++ pthread_t m_creatorThread;
++ std::string m_refName;
++ std::weak_ptr<QObjectRef> m_weakObjRef;
++
++ public:
++ using ObjectRefsMap = std::map<QObject *, std::shared_ptr<QObjectRef>>;
++ };
++
++ template<typename T>
++ class QThreadSafeRef : private QObjectThreadSafeRef
++ {
++ public:
++ static_assert(std::is_base_of<QObject, T>::value, "The class supports QObject subtypes only");
++
++ QThreadSafeRef();
++ QThreadSafeRef(QPointer<T> obj);
++
++ QThreadSafeRef(const QThreadSafeRef<T> &other);
++ QThreadSafeRef &operator=(const QThreadSafeRef<T> &other);
++
++ QObjectThreadSafeRef toQObjectThreadSafeRef() const;
++
++ std::string refName() const;
++
++ // it may be called only inside Qt thread that created the reference
++ QPointer<T> data() const;
++
++ void visitInQtThreadIfAlive(std::function<void(T &)> &&visitFunc) const;
++
++ private:
++ QObjectThreadSafeRef m_ref;
++ };
++
++ class QtState
++ {
++ public:
++ QtState(const QtState &) = delete;
++ QtState &operator=(const QtState &) = delete;
++
++ virtual ~QtState();
++
++ virtual bool isQtThread() const = 0;
++
++ virtual void invokeTask(std::function<void()> &&task) = 0;
++
++ protected:
++ QtState();
++ };
++
++ template<typename T>
++ QThreadSafeRef<T> makeQThreadSafeRef(T *obj);
++
++ // this function should be called once from Qt thread at some point during startup
++ Q_CORE_EXPORT void initQtThreadState();
++
++ // Creates a proxy for the base shared_ptr which uses custom deleter to ensure
++ // that the underlying shared_ptr is destroyed (synchronously) in the JS thread.
++ // This ensures that the managed object will be also destroyed in the JS thread,
++ // as long as the caller passes the only existing shared_ptr instance managing
++ // that object to the function. This is the caller's responsibility to do this
++ // (preferably by using a call to some std::shared_ptr<T> factory function as
++ // the function's argument).
++ template<typename T>
++ std::shared_ptr<T> makeProxyWithJsThreadDeleter(std::shared_ptr<T> &&baseSharedPtr);
++
++ // as makeProxyWithJsThreadDeleter(), but the release is posted to the JS thread instead of awaited
++ template<typename T>
++ std::shared_ptr<T> makeProxyWithAsyncJsThreadDeleter(std::shared_ptr<T> &&baseSharedPtr);
++
++ // invokes the task inside the Qt thread, can be called from any thread at any time
++ Q_CORE_EXPORT void invokeInQtThread(std::function<void()> &&task);
++
++ Q_CORE_EXPORT QtState &getQtState();
++
++ // logs error message with supplied prefix and error details extracted from
++ // JS error object passed as an argument via CallbackInfo
++ Q_CORE_EXPORT void logJsCallbackError(const QOhosCallbackInfo &cbInfo, const char *errorMessagePrefix);
++
++ // Creates JS callback that expects JS error object as a JS argument
++ // and logs details of the error. The logged message includes provided
++ // call context string (usually the name of called method).
++ Q_CORE_EXPORT std::function<void(const QOhosCallbackInfo &)> makeErrorLoggingJsCallback(std::string callContext);
++
++ template<typename T>
++ QThreadSafeRef<T> makeQThreadSafeRef(T *obj)
++ {
++ return QThreadSafeRef<T>(obj);
++ }
++
++ template<typename T>
++ QThreadSafeRef<T>::QThreadSafeRef() = default;
++
++ template<typename T>
++ QThreadSafeRef<T>::QThreadSafeRef(QPointer<T> obj)
++ : QObjectThreadSafeRef(obj.data())
++ {
++ }
++
++ template<typename T>
++ QThreadSafeRef<T>::QThreadSafeRef(const QThreadSafeRef<T> &other) = default;
++
++ template<typename T>
++ QThreadSafeRef<T> &QThreadSafeRef<T>::operator=(const QThreadSafeRef<T> &other) = default;
++
++ template<typename T>
++ std::string QThreadSafeRef<T>::refName() const
++ {
++ return QObjectThreadSafeRef::refName();
++ }
++
++ template<typename T>
++ QObjectThreadSafeRef QThreadSafeRef<T>::toQObjectThreadSafeRef() const
++ {
++ return static_cast<const QObjectThreadSafeRef &>(*this);
++ }
++
++ template<typename T>
++ QPointer<T> QThreadSafeRef<T>::data() const
++ {
++ return static_cast<T *>(
++ static_cast<QObject *>(
++ QObjectThreadSafeRef::data()));
++ }
++
++ template<typename T>
++ void QThreadSafeRef<T>::visitInQtThreadIfAlive(std::function<void(T &)> &&visitFunc) const
++ {
++ QObjectThreadSafeRef::visitInQtThreadIfAlive(
++ [sharedVisitFunc = takeFunctionToSharedPtr(visitFunc)](QObject &obj) {
++ const auto takenVisitFunc = std::exchange(*sharedVisitFunc, nullptr);
++ takenVisitFunc(static_cast<T &>(obj));
++ });
++ }
++
++ template<typename T>
++ std::shared_ptr<T> makeProxyWithJsThreadDeleter(std::shared_ptr<T> &&baseSharedPtr)
++ {
++ auto *baseRawPtr = baseSharedPtr.get();
++ return std::shared_ptr<T>(
++ baseRawPtr,
++ [baseSharedPtr = std::move(baseSharedPtr)](T *) mutable {
++ QOhosJsThreadGateway::runAndWait(
++ [&](QOhosJsState &) {
++ baseSharedPtr.reset();
++ },
++ Q_FUNC_INFO);
++ });
++ }
++
++ template<typename T>
++ std::shared_ptr<T> makeProxyWithAsyncJsThreadDeleter(std::shared_ptr<T> &&baseSharedPtr)
++ {
++ auto *baseRawPtr = baseSharedPtr.get();
++ return std::shared_ptr<T>(
++ baseRawPtr,
++ [baseSharedPtr = std::move(baseSharedPtr)](T *) mutable {
++ QOhosJsThreadGateway::invoke(
++ [baseSharedPtr = std::move(baseSharedPtr)](QOhosJsState &) mutable {
++ baseSharedPtr.reset();
++ });
++ });
++ }
++
++ }
++
++ template<typename T>
++ T QOhosJsState::eval(const std::string &expr, const std::vector<QNapi::ValueWrapper> &exprArgs)
++ {
++ using namespace std::string_literals;
++
++ QNapi::Object module;
++ std::string subExpr;
++ std::tie(module, subExpr) = extractModuleFromEvalExpr(expr);
++
++ return !subExpr.empty()
++ ? module.eval<T>(subExpr, exprArgs)
++ : QNapi::checkedCast<T>(
++ module,
++ [&]() {
++ return "module '"s + expr + "'"s;
++ });
++ }
++
++ inline QNapi::Promise QOhosJsState::evalToPromiseOrRejectOnThrow(
++ const std::string &expr, const std::vector<QNapi::ValueWrapper> &exprArgs)
++ {
++ QNapi::Object module;
++ std::string subExpr;
++ std::tie(module, subExpr) = extractModuleFromEvalExpr(expr);
++
++ return module.evalToPromiseOrRejectOnThrow(subExpr, exprArgs);
++ }
++
++ template<typename T>
++ std::enable_if_t<std::is_default_constructible<T>::value, T> &QOhosJsState::getAttachedObjectWithLazyCreate()
++ {
++ auto *objectPtr = reinterpret_cast<T *>(
++ getAttachedObjectWithLazyCreate(typeid(T), &std::make_shared<T>));
++ return *objectPtr;
++ }
++
++ template<typename Enum>
++ QNapi::Number QOhosJsState::mapOhosEnumToJs(Enum enumValue)
++ {
++ return mapOhosEnumToJs(static_cast<int>(enumValue), typeid(Enum), &makeOhosEnumInfo<Enum>);
++ }
++
++ template<typename Enum>
++ std::optional<Enum> QOhosJsState::tryMapOhosEnumFromJs(QNapi::Number enumJsValue)
++ {
++ auto intValue = tryMapOhosEnumFromJs(enumJsValue, typeid(Enum), &makeOhosEnumInfo<Enum>);
++ return intValue.has_value()
++ ? std::optional(static_cast<Enum>(intValue.value()))
++ : std::nullopt;
++ }
++
++ template<typename Enum>
++ Enum QOhosJsState::mapOhosEnumFromJs(QNapi::Number enumJsValue)
++ {
++ return static_cast<Enum>(mapOhosEnumFromJs(enumJsValue, typeid(Enum), &makeOhosEnumInfo<Enum>));
++ }
++
++ template<typename Enum>
++ QOhosJsState::OhosEnumInfo QOhosJsState::makeOhosEnumInfo()
++ {
++ static const auto enumEnumeratorsNames = QtOhos::OhosEnumMeta<Enum>::enumeratorsNames;
++
++ std::vector<std::pair<int, const char *>> intEnumeratorsNames;
++ std::transform(
++ enumEnumeratorsNames.begin(), enumEnumeratorsNames.end(),
++ std::back_inserter(intEnumeratorsNames),
++ [](const auto &valueNamePair) {
++ return std::pair<int, const char *>(static_cast<int>(valueNamePair.first), valueNamePair.second);
++ });
++
++ return OhosEnumInfo {
++ .fullTypeName = OhosEnumFullTypeNameFetcher<Enum>::fullTypeName(),
++ .enumeratorsNames = std::move(intEnumeratorsNames),
++ };
++ }
++
++ template<typename Func>
++ auto QOhosJsThreadGateway::eval(Func &&func, std::string callerContextName)
++ -> decltype(func(std::declval<QOhosJsState &>()))
++ {
++ using Result = decltype(func(std::declval<QOhosJsState &>()));
++ static_assert(
++ !(std::is_class<Result>::value
++ && (
++ std::is_convertible<Result, napi_value>::value
++ || std::is_convertible<Result, napi_ref>::value)),
++ "NAPI values/references must not be accessed outside the JS thread");
++
++ std::unique_ptr<Result> result;
++ runAndWait(
++ [&](QOhosJsState &jsState) {
++ result = std::make_unique<Result>(func(jsState));
++ },
++ std::move(callerContextName));
++ return std::move(*result);
++ }
++
++ template<typename T>
++ T QOhosJsThreadGateway::evalWithPromise(
++ std::function<void(QOhosJsState &, QOhosTaskPromise<T>)> evalFunc,
++ std::string callerContextName)
++ {
++ static_assert(
++ !(std::is_class<T>::value
++ && (
++ std::is_convertible<T, napi_value>::value
++ || std::is_convertible<T, napi_ref>::value)),
++ "NAPI values/references must not be accessed outside the JS thread");
++
++ std::unique_ptr<T> result;
++ invokeAndWaitForContinue(
++ [&](QOhosJsState &jsState, QOhosTaskPromise<> taskPromise) {
++ evalFunc(
++ jsState,
++ QOhosTaskPromise<T>(
++ [&result, taskPromise = std::move(taskPromise)](T value) {
++ result = std::make_unique<T>(std::move(value));
++ taskPromise();
++ },
++ {},
++ std::move(callerContextName)));
++ },
++ callerContextName);
++
++ return std::move(*result);
++ }
++
++ template<typename T>
++ T QOhosJsThreadGateway::evalWithConsumer(std::function<void(QOhosJsState &, std::function<void(T)>)> evalFunc)
++ {
++ return evalWithPromise<T>(
++ [evalFunc = std::move(evalFunc)](QOhosJsState &jsState, QOhosTaskPromise<T> resultPromise) {
++ evalFunc(jsState, std::move(resultPromise));
++ });
++ }
++
++ QT_END_NAMESPACE
++
++ #endif