From ab84c70f03ea20adbf27ee6011aa26f8900c5848 Mon Sep 17 00:00:00 2001
From: mappu <mappu04@gmail.com>
Date: Sun, 20 Oct 2024 18:19:37 +1300
Subject: [PATCH] qt6: initial support

---
 README.md                                     |   2 +-
 cmd/genbindings/exceptions.go                 | 121 +++++++++++++++---
 cmd/genbindings/intermediate.go               |   2 +-
 cmd/genbindings/main.go                       |  24 +++-
 docker/genbindings.Dockerfile                 |   1 +
 .../linux64-go1.19-qt6.4-dynamic.Dockerfile   |   6 +
 qt6/binding.go                                |   3 +
 qt6/cflags_linux.go                           |   9 ++
 8 files changed, 146 insertions(+), 22 deletions(-)
 create mode 100644 docker/linux64-go1.19-qt6.4-dynamic.Dockerfile
 create mode 100644 qt6/binding.go
 create mode 100644 qt6/cflags_linux.go

diff --git a/README.md b/README.md
index 693cb9fa..2ab47725 100644
--- a/README.md
+++ b/README.md
@@ -10,7 +10,7 @@
 
 MIQT is MIT-licensed Qt bindings for Go.
 
-This is a straightforward binding of the Qt API using CGO. You must have a working Qt C++ development toolchain to use this Go binding.
+This is a straightforward binding of the Qt 5.15 / Qt 6.4+ API using CGO. You must have a working Qt C++ development toolchain to use this Go binding.
 
 These bindings were newly started in August 2024. The bindings are functional for all of QtCore, QtGui, and QtWidgets, and there is a uic/rcc implementation. But, the bindings may be immature in some ways. Please try out the bindings and raise issues if you have trouble.
 
diff --git a/cmd/genbindings/exceptions.go b/cmd/genbindings/exceptions.go
index 058f93af..05237e8a 100644
--- a/cmd/genbindings/exceptions.go
+++ b/cmd/genbindings/exceptions.go
@@ -5,25 +5,56 @@ import (
 	"strings"
 )
 
-func InsertTypedefs() {
+func InsertTypedefs(qt6 bool) {
 
 	// Seed well-known typedefs
+	pp := "qt"
+	if qt6 {
+		pp = "qt6"
+	}
 
 	// QString is deleted from this binding
-	KnownTypedefs["QStringList"] = lookupResultTypedef{"qt", CppTypedef{"QStringList", parseSingleTypeString("QList<QString>")}}
+	KnownTypedefs["QStringList"] = lookupResultTypedef{pp, CppTypedef{"QStringList", parseSingleTypeString("QList<QString>")}}
 
 	// FIXME this isn't picked up automatically because QFile inherits QFileDevice and the name refers to its parent class
-	KnownTypedefs["QFile::FileTime"] = lookupResultTypedef{"qt", CppTypedef{"QFile::FileTime", parseSingleTypeString("QFileDevice::FileTime")}}
+	KnownTypedefs["QFile::FileTime"] = lookupResultTypedef{pp, CppTypedef{"QFile::FileTime", parseSingleTypeString("QFileDevice::FileTime")}}
 
-	// n.b. Qt 5 only
-	KnownTypedefs["QLineF::IntersectionType"] = lookupResultTypedef{"qt", CppTypedef{"QLineF::IntersectionType", parseSingleTypeString("QLineF::IntersectType")}}
+	if !qt6 {
+		// n.b. Qt 5 only
+		KnownTypedefs["QLineF::IntersectionType"] = lookupResultTypedef{pp, CppTypedef{"QLineF::IntersectionType", parseSingleTypeString("QLineF::IntersectType")}}
+	} else {
+		// Must be removed for Qt 6
+	}
 
 	// Not sure the reason for this one
-	KnownTypedefs["QSocketDescriptor::DescriptorType"] = lookupResultTypedef{"qt", CppTypedef{"QSocketDescriptor::DescriptorType", parseSingleTypeString("QSocketNotifier::Type")}}
+	KnownTypedefs["QSocketDescriptor::DescriptorType"] = lookupResultTypedef{pp, CppTypedef{"QSocketDescriptor::DescriptorType", parseSingleTypeString("QSocketNotifier::Type")}}
 
 	// QFile doesn't see QFileDevice parent class enum
-	KnownTypedefs["QFile::Permissions"] = lookupResultTypedef{"qt", CppTypedef{"QFile::Permissions", parseSingleTypeString("QFileDevice::Permissions")}}
-	KnownTypedefs["QFileDevice::Permissions"] = lookupResultTypedef{"qt", CppTypedef{"QFile::Permissions", parseSingleTypeString("QFlags<QFileDevice::Permission>")}}
+	KnownTypedefs["QFile::Permissions"] = lookupResultTypedef{pp, CppTypedef{"QFile::Permissions", parseSingleTypeString("QFileDevice::Permissions")}}
+	KnownTypedefs["QFileDevice::Permissions"] = lookupResultTypedef{pp, CppTypedef{"QFile::Permissions", parseSingleTypeString("QFlags<QFileDevice::Permission>")}}
+	KnownTypedefs["QIODevice::OpenMode"] = lookupResultTypedef{pp, CppTypedef{"QIODevice::OpenMode", parseSingleTypeString("QIODeviceBase::OpenMode")}}
+
+	if qt6 {
+		// Qt 6 QVariant helper types - needs investigation
+		KnownTypedefs["QVariantHash"] = lookupResultTypedef{"qt6", CppTypedef{"QVariantHash", parseSingleTypeString("QHash<QString,QVariant>")}}
+		KnownTypedefs["QVariantList"] = lookupResultTypedef{"qt6", CppTypedef{"QVariantList", parseSingleTypeString("QList<QVariant>")}}
+		KnownTypedefs["QVariantMap"] = lookupResultTypedef{"qt6", CppTypedef{"QVariantMap", parseSingleTypeString("QMap<QString,QVariant>")}}
+
+		// Qt 6 renamed the enum to LibraryPath, but left some uses of LibraryLocation with a typedef
+		// We don't find the typedef - needs investigation
+		// ONLY add this on Qt 6 builds, breaks Qt 5
+		KnownTypedefs["QLibraryInfo::LibraryLocation"] = lookupResultTypedef{"qt6", CppTypedef{"QLibraryInfo::LibraryLocation", parseSingleTypeString("QLibraryInfo::LibraryPath")}}
+
+		// Enums
+
+		// QSysInfo.h is being truncated and not finding any content
+		KnownEnums["QSysInfo::Endian"] = lookupResultEnum{"qt6", CppEnum{
+			EnumName: "QSysInfo::Endian",
+			UnderlyingType: CppParameter{
+				ParameterType: "int",
+			},
+		}}
+	}
 
 }
 
@@ -50,7 +81,13 @@ func AllowHeader(fullpath string) bool {
 		"qstring.h",                    // QString does not exist in this binding
 		"qbytearray.h",                 // QByteArray does not exist in this binding
 		"qlist.h",                      // QList does not exist in this binding
-		"qvector.h":                    // QVector does not exist in this binding
+		"qvector.h",                    // QVector does not exist in this binding
+		"qtcoreexports.h",              // Nothing bindable here and has Q_CORE_EXPORT definition issues
+		"q20algorithm.h",               // Qt 6 unstable header
+		"q20functional.h",              // Qt 6 unstable header
+		"q20iterator.h",                // Qt 6 unstable header
+		"q23functional.h",              // Qt 6 unstable header
+		"____last____":
 		return false
 	}
 
@@ -79,7 +116,9 @@ func ImportHeaderForClass(className string) bool {
 	case "QGraphicsEffectSource", // e.g. qgraphicseffect.h
 		"QAbstractConcatenable", // qstringbuilder.h
 		"QTextEngine",           // qtextlayout.h
-		"QText":                 // e.g. qtextcursor.h
+		"QText",                 // e.g. qtextcursor.h
+		"QVLABaseBase",          // e.g. Qt 6 qvarlengtharray.h
+		"____last____":
 		return false
 	}
 
@@ -102,12 +141,22 @@ func AllowClass(className string) bool {
 
 	switch className {
 	case
-		"QTextStreamManipulator", // Only seems to contain garbage methods
-		"QException",             // Extends std::exception, too hard
-		"QUnhandledException",    // As above (child class)
-		"QItemSelection",         // Extends a QList<>, too hard
-		"QXmlStreamAttributes",   // Extends a QList<>, too hard
-		"QPolygon", "QPolygonF":  // Extends a QVector<QPoint> template class, too hard
+		"QTextStreamManipulator",     // Only seems to contain garbage methods
+		"QException",                 // Extends std::exception, too hard
+		"QUnhandledException",        // As above (child class)
+		"QItemSelection",             // Extends a QList<>, too hard
+		"QXmlStreamAttributes",       // Extends a QList<>, too hard
+		"QPolygon",                   // Extends a QVector<QPoint> template class, too hard
+		"QPolygonF",                  // Extends a QVector<QPoint> template class, too hard
+		"QAssociativeIterator",       // Qt 6. Extends a QIterator<>, too hard
+		"QAssociativeConstIterator",  // Qt 6. Extends a QIterator<>, too hard
+		"QAssociativeIterable",       // Qt 6. Extends a QIterator<>, too hard
+		"QSequentialIterator",        // Qt 6. Extends a QIterator<>, too hard
+		"QSequentialConstIterator",   // Qt 6. Extends a QIterator<>, too hard
+		"QSequentialIterable",        // Qt 6. Extends a QIterator<>, too hard
+		"QBrushDataPointerDeleter",   // Qt 6 qbrush.h. Appears in header but cannot be linked
+		"QPropertyBindingPrivatePtr", // Qt 6 qpropertyprivate.h. Appears in header but cannot be linked
+		"____last____":
 		return false
 	}
 
@@ -115,8 +164,15 @@ func AllowClass(className string) bool {
 }
 
 func AllowSignal(mm CppMethod) bool {
+	if mm.ReturnType.ParameterType != "void" {
+		// This affects how we cast the signal function pointer for connect
+		// It would be fixable, but, real signals always have void return types anyway
+		return false
+	}
+
 	switch mm.MethodName {
-	case `metaObject`, `qt_metacast`:
+	case `metaObject`, `qt_metacast`,
+		`clone`: // Qt 6 - qcoreevent.h
 		return false
 	default:
 		return true
@@ -194,6 +250,19 @@ func CheckComplexity(p CppParameter, isReturnType bool) error {
 	if strings.HasPrefix(p.ParameterType, "FillResult<") {
 		return ErrTooComplex // Scintilla
 	}
+	if strings.HasPrefix(p.ParameterType, "QBindable<") {
+		return ErrTooComplex // e.g. Qt 6 qabstractanimation.h
+	}
+	if strings.HasPrefix(p.ParameterType, "QRgbaFloat<") {
+		return ErrTooComplex // e.g. Qt 6 qcolortransform.h
+	}
+	if strings.HasPrefix(p.ParameterType, "QPointer<") {
+		return ErrTooComplex // e.g. Qt 6 qevent.h . It should be possible to support this
+	}
+	if strings.HasPrefix(p.ParameterType, "EncodedData<") {
+		return ErrTooComplex // e.g. Qt 6 qstringconverter.h
+	}
+
 	if strings.HasPrefix(p.ParameterType, "std::") {
 		// std::initializer           e.g. qcborarray.h
 		// std::string                QByteArray->toStdString(). There are QString overloads already
@@ -212,12 +281,21 @@ func CheckComplexity(p CppParameter, isReturnType bool) error {
 	if strings.Contains(p.GetQtCppType().ParameterType, `::DataPtr`) {
 		return ErrTooComplex // e.g. QImage::data_ptr()
 	}
+	if strings.Contains(p.ParameterType, `::DataPointer`) {
+		return ErrTooComplex // Qt 6 qbytearray.h. This could probably be made to work
+	}
+	if strings.HasPrefix(p.ParameterType, `QArrayDataPointer<`) {
+		return ErrTooComplex // Qt 6 qbytearray.h. This could probably be made to work
+	}
 
 	// Some QFoo constructors take a QFooPrivate
 	// QIcon also returns a QIconPrivate
 	if p.ParameterType[0] == 'Q' && strings.HasSuffix(p.ParameterType, "Private") {
 		return ErrTooComplex
 	}
+	if strings.HasPrefix(p.ParameterType, "QtPrivate::") {
+		return ErrTooComplex // e.g. Qt 6 qbindingstorage.h
+	}
 
 	// If any parameters are QString*, skip the method
 	// QDebug constructor
@@ -260,6 +338,8 @@ func CheckComplexity(p CppParameter, isReturnType bool) error {
 		"QPolygon", "QPolygonF", // QPolygon extends a template type
 		"QGenericMatrix", "QMatrix3x3", // extends a template type
 		"QLatin1String", "QStringView", // e.g. QColor constructors and QColor::SetNamedColor() overloads. These are usually optional alternatives to QString
+		"QLatin1StringView",               // Qt 6 - used in qanystringview
+		"QUtf8StringView",                 // Qt 6 - used in qdebug
 		"QStringRef",                      // e.g. QLocale::toLongLong and similar overloads. As above
 		"qfloat16",                        // e.g. QDataStream - there is no such half-float type in C or Go
 		"char16_t",                        // e.g. QChar() constructor overload, just unnecessary
@@ -289,7 +369,12 @@ func CheckComplexity(p CppParameter, isReturnType bool) error {
 		"QPlatformScreen",                 // e.g. qscreen.h. as below
 		"QPlatformWindow",                 // e.g. qwindow.h, as below
 		"QPlatformSurface",                // e.g. qsurface.h. as below
-		"QPlatformMenu":                   // e.g. QMenu_PlatformMenu. Defined in the QPA, could probably expose as uintptr
+		"QPlatformMenu",                   // e.g. QMenu_PlatformMenu. Defined in the QPA, could probably expose as uintptr
+		"struct _XDisplay",                // Qt 6 QGuiApplication_platform
+		"xcb_connection_t",                // Qt 6 QGuiApplication_platform
+		"QTextDocument::ResourceProvider", // Qt 6 typedef for unsupported std::function<QVariant(const QUrl&)>
+		"QTransform::Affine",              // Qt 6 qtransform.h - public method returning private type
+		"____last____":
 		return ErrTooComplex
 	}
 
diff --git a/cmd/genbindings/intermediate.go b/cmd/genbindings/intermediate.go
index 76b601d7..590531bd 100644
--- a/cmd/genbindings/intermediate.go
+++ b/cmd/genbindings/intermediate.go
@@ -25,7 +25,7 @@ var (
 	KnownEnums      map[string]lookupResultEnum
 )
 
-func init() {
+func flushKnownTypes() {
 	KnownClassnames = make(map[string]lookupResultClass)
 	KnownTypedefs = make(map[string]lookupResultTypedef)
 	KnownEnums = make(map[string]lookupResultEnum)
diff --git a/cmd/genbindings/main.go b/cmd/genbindings/main.go
index 2b262248..87788a9f 100644
--- a/cmd/genbindings/main.go
+++ b/cmd/genbindings/main.go
@@ -99,6 +99,9 @@ func main() {
 
 	flag.Parse()
 
+	flushKnownTypes()
+	InsertTypedefs(false)
+
 	generate(
 		"qt",
 		[]string{
@@ -146,6 +149,25 @@ func main() {
 		*outDir,
 		(&clangMatchUnderPath{filepath.Join(*extraLibsDir, "scintilla")}).Match,
 	)
+
+	// FLUSH all known typedefs / ...
+
+	flushKnownTypes()
+	InsertTypedefs(true)
+
+	// Qt 6
+	generate(
+		"qt6",
+		[]string{
+			"/usr/include/x86_64-linux-gnu/qt6/QtCore",
+			"/usr/include/x86_64-linux-gnu/qt6/QtGui",
+			"/usr/include/x86_64-linux-gnu/qt6/QtWidgets",
+		},
+		*clang,
+		strings.Fields("--std=c++17 "+pkgConfigCflags("Qt6Widgets")),
+		*outDir,
+		ClangMatchSameHeaderDefinitionOnly,
+	)
 }
 
 func generate(packageName string, srcDirs []string, clangBin string, cflags []string, outDir string, matcher ClangMatcher) {
@@ -170,8 +192,6 @@ func generate(packageName string, srcDirs []string, clangBin string, cflags []st
 		preserve: make(map[string]*CppEnum),
 	}
 
-	InsertTypedefs()
-
 	//
 	// PASS 0 (Fill clang cache)
 	//
diff --git a/docker/genbindings.Dockerfile b/docker/genbindings.Dockerfile
index 6ef7b241..9286fef2 100644
--- a/docker/genbindings.Dockerfile
+++ b/docker/genbindings.Dockerfile
@@ -4,6 +4,7 @@ RUN DEBIAN_FRONTEND=noninteractive apt-get update && \
     apt-get install --no-install-recommends -qyy \
         golang-go \
         qtbase5-dev \
+        qt6-base-dev \
         libqscintilla2-qt5-dev \
         clang \
         git \
diff --git a/docker/linux64-go1.19-qt6.4-dynamic.Dockerfile b/docker/linux64-go1.19-qt6.4-dynamic.Dockerfile
new file mode 100644
index 00000000..34af9e7e
--- /dev/null
+++ b/docker/linux64-go1.19-qt6.4-dynamic.Dockerfile
@@ -0,0 +1,6 @@
+FROM debian:bookworm
+
+RUN DEBIAN_FRONTEND=noninteractive apt-get update && \
+    apt-get install -qyy golang-go qt6-base-dev && \
+    apt-get clean
+
diff --git a/qt6/binding.go b/qt6/binding.go
new file mode 100644
index 00000000..f347d893
--- /dev/null
+++ b/qt6/binding.go
@@ -0,0 +1,3 @@
+package qt6
+
+type QSysInfo__Endian int
diff --git a/qt6/cflags_linux.go b/qt6/cflags_linux.go
new file mode 100644
index 00000000..ed37ed15
--- /dev/null
+++ b/qt6/cflags_linux.go
@@ -0,0 +1,9 @@
+// +build linux,!android
+
+package qt6
+
+/*
+#cgo CFLAGS: -fPIC
+#cgo pkg-config: Qt6Widgets
+*/
+import "C"