diff --git a/src/native/QmlNet/QmlNet.pri b/src/native/QmlNet/QmlNet.pri index d53368e0..f1c83d72 100644 --- a/src/native/QmlNet/QmlNet.pri +++ b/src/native/QmlNet/QmlNet.pri @@ -1,4 +1,4 @@ -QT += gui qml core-private quickcontrols2 +QT += gui qml core-private quickcontrols2 widgets CONFIG(enable-webengine) { QT += webengine diff --git a/src/native/QmlNet/QmlNet/qml/QCoreApplication.cpp b/src/native/QmlNet/QmlNet/qml/QCoreApplication.cpp new file mode 100644 index 00000000..a8b8ede5 --- /dev/null +++ b/src/native/QmlNet/QmlNet/qml/QCoreApplication.cpp @@ -0,0 +1,132 @@ +#include +#include +#include +#include + +GuiThreadContextTriggerCallback::GuiThreadContextTriggerCallback() : + callback(nullptr) { +} + +void GuiThreadContextTriggerCallback::trigger() { + if (callback) { + callback(); + } +} + +extern "C" { + +Q_DECL_EXPORT QGuiApplicationContainer* qapp_fromExisting(QCoreApplication* rawPointer) +{ + QGuiApplicationContainer* result = new QGuiApplicationContainer(); + result->ownsApp = false; + result->app = rawPointer; + + result->callback = QSharedPointer(new GuiThreadContextTriggerCallback()); + + return result; +} + +Q_DECL_EXPORT QGuiApplicationContainer* qapp_create(NetVariantListContainer* argsContainer, int flags, int type) +{ + QGuiApplicationContainer* result = new QGuiApplicationContainer(); + + // Build our args + if(argsContainer != nullptr) { + QSharedPointer args = argsContainer->list; + for(int x = 0; x < args->count(); x++) { + QByteArray arg = args->get(x)->getString().toLatin1(); + result->args.append(arg); + char* cstr = new char [size_t(arg.size())+1]; + memcpy(cstr, arg.data(), size_t(arg.size())+1); + result->argsPointer.push_back(cstr); + } + result->argCount = result->args.size(); + } else { + result->argCount = 0; + } + + result->ownsApp = true; + + switch(type) + { + case 0: + result->app = new QCoreApplication(result->argCount, &result->argsPointer[0], flags); + break; + case 1: + result->app = new QGuiApplication(result->argCount, &result->argsPointer[0], flags); + break; + case 2: + result->app = new QApplication(result->argCount, &result->argsPointer[0], flags); + break; + default: + qCritical("invalid app type %d", type); + delete result; + return nullptr; + } + + result->callback = QSharedPointer(new GuiThreadContextTriggerCallback()); + + return result; +} + +Q_DECL_EXPORT void qapp_destroy(QGuiApplicationContainer* container) +{ + for (auto i : container->argsPointer) { + delete[] i; + } + container->callback.clear(); + if(container->ownsApp) { + delete container->app; + } + delete container; +} + +Q_DECL_EXPORT int qapp_getType(QGuiApplicationContainer* container, QCoreApplication* rawPointer) +{ + if (!container && !rawPointer) { + qCritical("invalid container and/or rawPointer"); + return -1; + } + if (container && rawPointer) { + qCritical("invalid container and/or rawPointer"); + return -1; + } + if(!rawPointer && container) { + rawPointer = container->app; + } + if (qobject_cast(rawPointer) != nullptr){ + return 2; + } + if (qobject_cast(rawPointer) != nullptr){ + return 1; + } + // QCoreApplication + return 0; +} + +Q_DECL_EXPORT int qapp_exec() +{ + return QGuiApplication::exec(); +} + +Q_DECL_EXPORT void qapp_addTriggerCallback(QGuiApplicationContainer* container, guiThreadTriggerCb callback) +{ + container->callback->callback = callback; +} + +Q_DECL_EXPORT void qapp_requestTrigger(QGuiApplicationContainer* container) +{ + QMetaObject::invokeMethod(container->callback.data(), "trigger", Qt::QueuedConnection); +} + +Q_DECL_EXPORT void qapp_exit(int returnCode) +{ + QGuiApplication::exit(returnCode); +} + +Q_DECL_EXPORT QCoreApplication* qapp_internalPointer(QGuiApplicationContainer* container) +{ + return container->app; +} + +} diff --git a/src/native/QmlNet/QmlNet/qml/QGuiApplication.h b/src/native/QmlNet/QmlNet/qml/QCoreApplication.h similarity index 91% rename from src/native/QmlNet/QmlNet/qml/QGuiApplication.h rename to src/native/QmlNet/QmlNet/qml/QCoreApplication.h index e0ea6902..baa90e93 100644 --- a/src/native/QmlNet/QmlNet/qml/QGuiApplication.h +++ b/src/native/QmlNet/QmlNet/qml/QCoreApplication.h @@ -20,8 +20,8 @@ struct QGuiApplicationContainer { int argCount; QList args; std::vector argsPointer; - QGuiApplication* guiApp; - bool ownsGuiApp; + QCoreApplication* app; + bool ownsApp; QSharedPointer callback; }; diff --git a/src/native/QmlNet/QmlNet/qml/QGuiApplication.cpp b/src/native/QmlNet/QmlNet/qml/QGuiApplication.cpp deleted file mode 100644 index 0773cc98..00000000 --- a/src/native/QmlNet/QmlNet/qml/QGuiApplication.cpp +++ /dev/null @@ -1,78 +0,0 @@ -#include -#include -#include - -GuiThreadContextTriggerCallback::GuiThreadContextTriggerCallback() : - callback(nullptr) { -} - -void GuiThreadContextTriggerCallback::trigger() { - if (callback) { - callback(); - } -} - -extern "C" { - -Q_DECL_EXPORT QGuiApplicationContainer* qguiapplication_create(NetVariantListContainer* argsContainer, QGuiApplication* existingApp) { - QGuiApplicationContainer* result = new QGuiApplicationContainer(); - - if (existingApp != nullptr) { - result->ownsGuiApp = false; - result->guiApp = existingApp; - } else { - result->ownsGuiApp = true; - // Build our args - if(argsContainer != nullptr) { - QSharedPointer args = argsContainer->list; - for(int x = 0; x < args->count(); x++) { - QByteArray arg = args->get(x)->getString().toLatin1(); - result->args.append(arg); - char* cstr = new char [size_t(arg.size())+1]; - memcpy(cstr, arg.data(), size_t(arg.size())+1); - result->argsPointer.push_back(cstr); - } - result->argCount = result->args.size(); - } else { - result->argCount = 0; - } - result->guiApp = new QGuiApplication(result->argCount, &result->argsPointer[0], 0); - } - - result->callback = QSharedPointer(new GuiThreadContextTriggerCallback()); - - return result; -} - -Q_DECL_EXPORT void qguiapplication_destroy(QGuiApplicationContainer* container) { - for (auto i : container->argsPointer) { - delete[] i; - } - container->callback.clear(); - if(container->ownsGuiApp) { - delete container->guiApp; - } - delete container; -} - -Q_DECL_EXPORT int qguiapplication_exec() { - return QGuiApplication::exec(); -} - -Q_DECL_EXPORT void qguiapplication_addTriggerCallback(QGuiApplicationContainer* container, guiThreadTriggerCb callback) { - container->callback->callback = callback; -} - -Q_DECL_EXPORT void qguiapplication_requestTrigger(QGuiApplicationContainer* container) { - QMetaObject::invokeMethod(container->callback.data(), "trigger", Qt::QueuedConnection); -} - -Q_DECL_EXPORT void qguiapplication_exit(int returnCode) { - QGuiApplication::exit(returnCode); -} - -Q_DECL_EXPORT QGuiApplication* qguiapplication_internalPointer(QGuiApplicationContainer* container) { - return container->guiApp; -} - -} diff --git a/src/native/QmlNet/QmlNet/qml/qml.pri b/src/native/QmlNet/QmlNet/qml/qml.pri index a0097949..e6949bec 100644 --- a/src/native/QmlNet/QmlNet/qml/qml.pri +++ b/src/native/QmlNet/QmlNet/qml/qml.pri @@ -1,5 +1,4 @@ HEADERS += \ - $$PWD/QGuiApplication.h \ $$PWD/QQmlApplicationEngine.h \ $$PWD/NetVariant.h \ $$PWD/NetValue.h \ @@ -14,10 +13,10 @@ HEADERS += \ $$PWD/NetValueMetaObjectPacker.h \ $$PWD/QCommon.h \ $$PWD/NetListModel.h \ - $$PWD/QtWebEngine.h + $$PWD/QtWebEngine.h \ + $$PWD/QCoreApplication.h SOURCES += \ - $$PWD/QGuiApplication.cpp \ $$PWD/QQmlApplicationEngine.cpp \ $$PWD/NetVariant.cpp \ $$PWD/NetValue.cpp \ @@ -32,4 +31,5 @@ SOURCES += \ $$PWD/NetValueMetaObjectPacker.cpp \ $$PWD/QCommon.cpp \ $$PWD/NetListModel.cpp \ - $$PWD/QtWebEngine.cpp + $$PWD/QtWebEngine.cpp \ + $$PWD/QCoreApplication.cpp diff --git a/src/net/Qml.Net/Host.cs b/src/net/Qml.Net/Host.cs index 034248e3..898a0d47 100644 --- a/src/net/Qml.Net/Host.cs +++ b/src/net/Qml.Net/Host.cs @@ -3,6 +3,7 @@ using System.Linq; using System.Runtime.InteropServices; using NetNativeLibLoader.Loader; using NetNativeLibLoader.PathResolver; +using Qml.Net.Internal; namespace Qml.Net { @@ -47,7 +48,7 @@ namespace Qml.Net public delegate int NetRunCallbackDelegate(); - public static int Run(string[] args, Func action) + public static int Run(string[] args, Func action) { if (args.Length < 4) { @@ -60,7 +61,23 @@ namespace Qml.Net var exportedSymbolPtr = new IntPtr((long)ulong.Parse(args[3])); GetExportedSymbol = Marshal.GetDelegateForFunctionPointer(exportedSymbolPtr); - using (var app = new QGuiApplication(appPtr)) + QCoreApplication app = null; + switch (Interop.QCoreApplication.GetAppType(IntPtr.Zero, appPtr)) + { + case 0: + app = new QCoreApplication(appPtr); + break; + case 1: + app = new QGuiApplication(appPtr); + break; + case 2: + app = new QApplication(appPtr); + break; + default: + throw new Exception("Invalid app type"); + } + + using (app) { using (var engine = new QQmlApplicationEngine(enginePtr)) { diff --git a/src/net/Qml.Net/Internal/Interop.cs b/src/net/Qml.Net/Internal/Interop.cs index f834fcd0..3bb39882 100644 --- a/src/net/Qml.Net/Internal/Interop.cs +++ b/src/net/Qml.Net/Internal/Interop.cs @@ -99,7 +99,7 @@ namespace Qml.Net.Internal NetMethodInfo = LoadInteropType(library, loader); NetPropertyInfo = LoadInteropType(library, loader); NetTypeManager = LoadInteropType(library, loader); - QGuiApplication = LoadInteropType(library, loader); + QCoreApplication = LoadInteropType(library, loader); QQmlApplicationEngine = LoadInteropType(library, loader); NetVariant = LoadInteropType(library, loader); NetReference = LoadInteropType(library, loader); @@ -136,7 +136,7 @@ namespace Qml.Net.Internal public static NetTypeManagerInterop NetTypeManager { get; } - public static QGuiApplicationInterop QGuiApplication { get; } + public static QCoreApplicationInterop QCoreApplication { get; } public static QQmlApplicationEngineInterop QQmlApplicationEngine { get; } diff --git a/src/net/Qml.Net/QApplication.cs b/src/net/Qml.Net/QApplication.cs new file mode 100644 index 00000000..cb25d1b7 --- /dev/null +++ b/src/net/Qml.Net/QApplication.cs @@ -0,0 +1,25 @@ +using System; +using System.Runtime.InteropServices; +using System.Threading; +using Qml.Net.Internal; + +namespace Qml.Net +{ + public class QApplication : QCoreApplication + { + public QApplication() + : this(null) + { + } + + public QApplication(string[] args, int flags = 0) + : base(1, args, flags) + { + } + + internal QApplication(IntPtr existingApp) + : base(existingApp) + { + } + } +} \ No newline at end of file diff --git a/src/net/Qml.Net/QCoreApplication.cs b/src/net/Qml.Net/QCoreApplication.cs new file mode 100644 index 00000000..2a922e2d --- /dev/null +++ b/src/net/Qml.Net/QCoreApplication.cs @@ -0,0 +1,202 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Threading; +using Qml.Net.Internal; +using Qml.Net.Internal.Qml; + +namespace Qml.Net +{ + public class QCoreApplication : BaseDisposable + { + private readonly Queue _actionQueue = new Queue(); + private readonly SynchronizationContext _oldSynchronizationContext; + private GCHandle _triggerHandle; + + protected QCoreApplication(IntPtr handle, bool ownsHandle) + : base(handle, ownsHandle) + { + } + + public QCoreApplication() + : this(null) + { + } + + public QCoreApplication(string[] args, int flags = 0) + : this(0, args, flags) + { + } + + internal QCoreApplication(int type, string[] args, int flags) + : base(Create(type, args?.ToList(), flags)) + { + TriggerDelegate triggerDelegate = Trigger; + _triggerHandle = GCHandle.Alloc(triggerDelegate); + + Interop.QCoreApplication.AddTriggerCallback(Handle, Marshal.GetFunctionPointerForDelegate(triggerDelegate)); + + _oldSynchronizationContext = SynchronizationContext.Current; + SynchronizationContext.SetSynchronizationContext(new QtSynchronizationContext(this)); + } + + internal QCoreApplication(IntPtr existingApp) + : base(CreateFromExisting(existingApp)) + { + TriggerDelegate triggerDelegate = Trigger; + _triggerHandle = GCHandle.Alloc(triggerDelegate); + + Interop.QCoreApplication.AddTriggerCallback(Handle, Marshal.GetFunctionPointerForDelegate(triggerDelegate)); + + _oldSynchronizationContext = SynchronizationContext.Current; + SynchronizationContext.SetSynchronizationContext(new QtSynchronizationContext(this)); + } + + public int Exec() + { + return Interop.QCoreApplication.Exec(); + } + + public void Dispatch(Action action) + { + lock (_actionQueue) + { + _actionQueue.Enqueue(action); + } + RequestTrigger(); + } + + public void Exit(int returnCode = 0) + { + Interop.QCoreApplication.Exit(returnCode); + } + + public void Quit() + { + Exit(); + } + + private void RequestTrigger() + { + Interop.QCoreApplication.RequestTrigger(Handle); + } + + internal IntPtr InternalPointer => Interop.QCoreApplication.InternalPointer(Handle); + + private void Trigger() + { + Action action; + lock (_actionQueue) + { + action = _actionQueue.Dequeue(); + } + action?.Invoke(); + } + + protected override void DisposeUnmanaged(IntPtr ptr) + { + SynchronizationContext.SetSynchronizationContext(_oldSynchronizationContext); + Interop.QCoreApplication.Destroy(ptr); + _triggerHandle.Free(); + } + + private static IntPtr CreateFromExisting(IntPtr app) + { + return Interop.QCoreApplication.FromExisting(app); + } + + private static IntPtr Create(int type, List args, int flags) + { + if (args == null) + { + args = new List(); + } + + // By default, the argv[0] should be the process name. + // .NET doesn't pass that name, but Qt should get it + // since it does in a normal Qt environment. + args.Insert(0, System.Diagnostics.Process.GetCurrentProcess().ProcessName); + + using (var strings = new NetVariantList()) + { + foreach (var arg in args) + { + using (var variant = new NetVariant()) + { + variant.String = arg; + strings.Add(variant); + } + } + + return Interop.QCoreApplication.Create(strings.Handle, flags, type); + } + } + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + private delegate void TriggerDelegate(); + + private class QtSynchronizationContext : SynchronizationContext + { + readonly QCoreApplication _app; + + public QtSynchronizationContext(QCoreApplication guiApp) + { + _app = guiApp; + } + + public override void Post(SendOrPostCallback d, object state) + { + _app.Dispatch(() => d.Invoke(state)); + } + } + } + + internal class QCoreApplicationInterop + { + [NativeSymbol(Entrypoint = "qapp_fromExisting")] + public FromExistingDel FromExisting { get; set; } + + public delegate IntPtr FromExistingDel(IntPtr rawPointer); + + [NativeSymbol(Entrypoint = "qapp_create")] + public CreateDel Create { get; set; } + + public delegate IntPtr CreateDel(IntPtr args, int flags, int type); + + [NativeSymbol(Entrypoint = "qapp_destroy")] + public DestroyDel Destroy { get; set; } + + [NativeSymbol(Entrypoint = "qapp_getType")] + public GetAppTypeDel GetAppType { get; set; } + + public delegate int GetAppTypeDel(IntPtr container, IntPtr rawPointer); + + public delegate void DestroyDel(IntPtr container); + + [NativeSymbol(Entrypoint = "qapp_exec")] + public ExecDel Exec { get; set; } + + public delegate int ExecDel(); + + [NativeSymbol(Entrypoint = "qapp_addTriggerCallback")] + public AddTriggerCallbackDel AddTriggerCallback { get; set; } + + public delegate void AddTriggerCallbackDel(IntPtr app, IntPtr callback); + + [NativeSymbol(Entrypoint = "qapp_requestTrigger")] + public RequestTriggerDel RequestTrigger { get; set; } + + public delegate void RequestTriggerDel(IntPtr app); + + [NativeSymbol(Entrypoint = "qapp_exit")] + public ExitDel Exit { get; set; } + + public delegate void ExitDel(int returnCode); + + [NativeSymbol(Entrypoint = "qapp_internalPointer")] + public InternalPointerDel InternalPointer { get; set; } + + public delegate IntPtr InternalPointerDel(IntPtr app); + } +} \ No newline at end of file diff --git a/src/net/Qml.Net/QGuiApplication.cs b/src/net/Qml.Net/QGuiApplication.cs index 13f8d0ff..f6bce3b0 100644 --- a/src/net/Qml.Net/QGuiApplication.cs +++ b/src/net/Qml.Net/QGuiApplication.cs @@ -8,175 +8,21 @@ using Qml.Net.Internal.Qml; namespace Qml.Net { - public sealed class QGuiApplication : BaseDisposable + public class QGuiApplication : QCoreApplication { - readonly Queue _actionQueue = new Queue(); - GCHandle _triggerHandle; - readonly SynchronizationContext _oldSynchronizationContext; - public QGuiApplication() : this(null) { } - public QGuiApplication(string[] args) - : base(Create(args?.ToList())) + public QGuiApplication(string[] args, int flags = 0) + : base(1, args, flags) { - TriggerDelegate triggerDelegate = Trigger; - _triggerHandle = GCHandle.Alloc(triggerDelegate); - - Interop.QGuiApplication.AddTriggerCallback(Handle, Marshal.GetFunctionPointerForDelegate(triggerDelegate)); - - _oldSynchronizationContext = SynchronizationContext.Current; - SynchronizationContext.SetSynchronizationContext(new QtSynchronizationContext(this)); } internal QGuiApplication(IntPtr existingApp) - : base(CreateFromExisting(existingApp)) + : base(existingApp) { - TriggerDelegate triggerDelegate = Trigger; - _triggerHandle = GCHandle.Alloc(triggerDelegate); - - Interop.QGuiApplication.AddTriggerCallback(Handle, Marshal.GetFunctionPointerForDelegate(triggerDelegate)); - - _oldSynchronizationContext = SynchronizationContext.Current; - SynchronizationContext.SetSynchronizationContext(new QtSynchronizationContext(this)); } - - public int Exec() - { - return Interop.QGuiApplication.Exec(); - } - - public void Dispatch(Action action) - { - lock (_actionQueue) - { - _actionQueue.Enqueue(action); - } - RequestTrigger(); - } - - public void Exit(int returnCode = 0) - { - Interop.QGuiApplication.Exit(returnCode); - } - - public void Quit() - { - Exit(); - } - - private void RequestTrigger() - { - Interop.QGuiApplication.RequestTrigger(Handle); - } - - internal IntPtr InternalPointer => Interop.QGuiApplication.InternalPointer(Handle); - - private void Trigger() - { - Action action; - lock (_actionQueue) - { - action = _actionQueue.Dequeue(); - } - action?.Invoke(); - } - - protected override void DisposeUnmanaged(IntPtr ptr) - { - SynchronizationContext.SetSynchronizationContext(_oldSynchronizationContext); - Interop.QGuiApplication.Destroy(ptr); - _triggerHandle.Free(); - } - - private static IntPtr CreateFromExisting(IntPtr app) - { - return Interop.QGuiApplication.Create(IntPtr.Zero, app); - } - - private static IntPtr Create(List args) - { - if (args == null) - { - args = new List(); - } - - // By default, the argv[0] should be the process name. - // .NET doesn't pass that name, but Qt should get it - // since it does in a normal Qt environment. - args.Insert(0, System.Diagnostics.Process.GetCurrentProcess().ProcessName); - - using (var strings = new NetVariantList()) - { - foreach (var arg in args) - { - using (var variant = new NetVariant()) - { - variant.String = arg; - strings.Add(variant); - } - } - - return Interop.QGuiApplication.Create(strings.Handle, IntPtr.Zero); - } - } - - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - private delegate void TriggerDelegate(); - - private class QtSynchronizationContext : SynchronizationContext - { - readonly QGuiApplication _guiApp; - - public QtSynchronizationContext(QGuiApplication guiApp) - { - _guiApp = guiApp; - } - - public override void Post(SendOrPostCallback d, object state) - { - _guiApp.Dispatch(() => d.Invoke(state)); - } - } - } - - internal class QGuiApplicationInterop - { - [NativeSymbol(Entrypoint = "qguiapplication_create")] - public CreateDel Create { get; set; } - - public delegate IntPtr CreateDel(IntPtr args, IntPtr existingApp); - - [NativeSymbol(Entrypoint = "qguiapplication_destroy")] - public DestroyDel Destroy { get; set; } - - public delegate void DestroyDel(IntPtr app); - - [NativeSymbol(Entrypoint = "qguiapplication_exec")] - public ExecDel Exec { get; set; } - - public delegate int ExecDel(); - - [NativeSymbol(Entrypoint = "qguiapplication_addTriggerCallback")] - public AddTriggerCallbackDel AddTriggerCallback { get; set; } - - public delegate void AddTriggerCallbackDel(IntPtr app, IntPtr callback); - - [NativeSymbol(Entrypoint = "qguiapplication_requestTrigger")] - public RequestTriggerDel RequestTrigger { get; set; } - - public delegate void RequestTriggerDel(IntPtr app); - - [NativeSymbol(Entrypoint = "qguiapplication_exit")] - public ExitDel Exit { get; set; } - - public delegate void ExitDel(int returnCode); - - [NativeSymbol(Entrypoint = "qguiapplication_internalPointer")] - public InternalPointerDel InternalPointer { get; set; } - - public delegate IntPtr InternalPointerDel(IntPtr app); } } \ No newline at end of file